From 4ec6f7ba15b6df444eba4a56759fc9f195f73868 Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Tue, 26 Nov 2024 21:43:42 +0200 Subject: [PATCH 001/420] feat: allow passing component for description in ContentHeader sometimes it's necessary to add other than plain typography in the description for ContentHeader. Signed-off-by: Heikki Hellgren --- .changeset/nervous-onions-complain.md | 5 +++ packages/core-components/report-alpha.api.md | 2 +- .../layout/ContentHeader/ContentHeader.tsx | 38 ++++++++++++++++--- 3 files changed, 38 insertions(+), 7 deletions(-) create mode 100644 .changeset/nervous-onions-complain.md diff --git a/.changeset/nervous-onions-complain.md b/.changeset/nervous-onions-complain.md new file mode 100644 index 0000000000..39fd089222 --- /dev/null +++ b/.changeset/nervous-onions-complain.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Allow passing component for `ContentHeader` description diff --git a/packages/core-components/report-alpha.api.md b/packages/core-components/report-alpha.api.md index 1f4b999232..2984c1c72f 100644 --- a/packages/core-components/report-alpha.api.md +++ b/packages/core-components/report-alpha.api.md @@ -14,10 +14,10 @@ export const coreComponentsTranslationRef: TranslationRef< readonly 'signIn.title': 'Sign In'; readonly 'signIn.loginFailed': 'Login failed'; readonly 'signIn.customProvider.title': 'Custom User'; + readonly 'signIn.customProvider.continue': 'Continue'; readonly 'signIn.customProvider.subtitle': 'Enter your own User ID and credentials.\n This selection will not be stored.'; readonly 'signIn.customProvider.userId': 'User ID'; readonly 'signIn.customProvider.tokenInvalid': 'Token is not a valid OpenID Connect JWT Token'; - readonly 'signIn.customProvider.continue': 'Continue'; readonly 'signIn.customProvider.idToken': 'ID Token (optional)'; readonly 'signIn.guestProvider.title': 'Guest'; readonly 'signIn.guestProvider.enter': 'Enter'; diff --git a/packages/core-components/src/layout/ContentHeader/ContentHeader.tsx b/packages/core-components/src/layout/ContentHeader/ContentHeader.tsx index e2a6e70620..1e8ab5e3ff 100644 --- a/packages/core-components/src/layout/ContentHeader/ContentHeader.tsx +++ b/packages/core-components/src/layout/ContentHeader/ContentHeader.tsx @@ -84,10 +84,30 @@ const ContentHeaderTitle = ({ title, className }: ContentHeaderTitleProps) => ( ); +type ContentHeaderDescriptionProps = { + description?: string; + className?: string; +}; + +const ContentHeaderDescription = ({ + description, + className, +}: ContentHeaderDescriptionProps) => + description ? ( + + {description} + + ) : null; + type ContentHeaderProps = { title?: ContentHeaderTitleProps['title']; titleComponent?: ReactNode; - description?: string; + description?: ContentHeaderDescriptionProps['description']; + descriptionComponent?: ReactNode; textAlign?: 'left' | 'right' | 'center'; }; @@ -104,6 +124,7 @@ export function ContentHeader(props: PropsWithChildren) { title, titleComponent: TitleComponent = undefined, children, + descriptionComponent: DescriptionComponent = undefined, textAlign = 'left', } = props; const classes = useStyles({ textAlign })(); @@ -114,17 +135,22 @@ export function ContentHeader(props: PropsWithChildren) { ); + const renderedDescription = DescriptionComponent ? ( + DescriptionComponent + ) : ( + + ); + return ( <> {renderedTitle} - {description && ( - - {description} - - )} + {renderedDescription} {children} From cffde24a15c4f9b7e5936288603af09671eb5773 Mon Sep 17 00:00:00 2001 From: nikolar Date: Tue, 26 Nov 2024 17:15:40 -0800 Subject: [PATCH 002/420] add props to customize CustomHome Signed-off-by: nikolar --- plugins/techdocs/package.json | 1 + .../home/components/DefaultTechDocsHome.tsx | 35 +++++-- .../home/components/Grids/InfoCardGrid.tsx | 94 +++++++++++++++++++ .../src/home/components/Grids/index.ts | 1 + .../home/components/TechDocsCustomHome.tsx | 65 +++++++++---- .../src/home/components/TechDocsIndexPage.tsx | 11 ++- .../home/components/TechDocsPageWrapper.tsx | 17 ++-- plugins/techdocs/src/overridableComponents.ts | 36 +++++++ yarn.lock | 1 + 9 files changed, 227 insertions(+), 34 deletions(-) create mode 100644 plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx create mode 100644 plugins/techdocs/src/overridableComponents.ts diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 7032277a95..d443b8ebe7 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -59,6 +59,7 @@ "test": "backstage-cli package test" }, "dependencies": { + "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/core-compat-api": "workspace:^", diff --git a/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx b/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx index 2fbd5bb50a..f4a4e7862a 100644 --- a/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx +++ b/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx @@ -46,15 +46,28 @@ export type DefaultTechDocsHomeProps = TechDocsIndexPageProps; * @public */ export const DefaultTechDocsHome = (props: TechDocsIndexPageProps) => { - const { initialFilter = 'owned', columns, actions, ownerPickerMode } = props; + const { + initialFilter = 'owned', + columns, + actions, + ownerPickerMode, + showHeader, + options, + title, + subtitle, + hideSupport, + } = props; + const Wrapper = showHeader !== false ? TechDocsPageWrapper : React.Fragment; return ( - + - - - Discover documentation in your ecosystem. - - + {hideSupport !== true && ( + + + Discover documentation in your ecosystem. + + + )} @@ -64,11 +77,15 @@ export const DefaultTechDocsHome = (props: TechDocsIndexPageProps) => { - + - + ); }; diff --git a/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx b/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx new file mode 100644 index 0000000000..46293f9228 --- /dev/null +++ b/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx @@ -0,0 +1,94 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { rootDocsRouteRef } from '../../../routes'; +import { toLowerMaybe } from '../../../helpers'; +import { Entity } from '@backstage/catalog-model'; +import { useApi, useRouteRef, configApiRef } from '@backstage/core-plugin-api'; +import { ItemCardGrid, InfoCard, Link } from '@backstage/core-components'; +import { makeStyles } from '@material-ui/core/styles'; +import React from 'react'; + +/** @public */ +export type InfoCardGridClassKey = 'linkSpacer' | 'readMoreLink'; + +const useStyles = makeStyles( + theme => ({ + linkSpacer: { + paddingTop: theme.spacing(0.2), + }, + readMoreLink: { + paddingTop: theme.spacing(0.2), + }, + }), + { name: 'BackstageInfoCardGrid' }, +); + +/** + * Props for {@link InfoCardGird} + * + * @public + */ +export type InfoCardGirdProps = { + entities: Entity[] | undefined; + linkContent?: string | JSX.Element; + linkDest?: (entity: Entity) => string; +}; + +/** + * Component which accepts a list of entities and renders a info card for each entity + * + * @public + */ +export const InfoCardGird = (props: InfoCardGirdProps) => { + const { entities, linkContent, linkDest } = props; + const classes = useStyles(); + const getRouteToReaderPageFor = useRouteRef(rootDocsRouteRef); + const config = useApi(configApiRef); + if (!entities) return null; + return ( + + {!entities?.length + ? null + : entities.map(entity => ( + +
{entity?.metadata?.description}
+
+ + {linkContent || 'Read Docs'} + + + ))} + + ); +}; diff --git a/plugins/techdocs/src/home/components/Grids/index.ts b/plugins/techdocs/src/home/components/Grids/index.ts index c28b5a0723..136e31d569 100644 --- a/plugins/techdocs/src/home/components/Grids/index.ts +++ b/plugins/techdocs/src/home/components/Grids/index.ts @@ -16,3 +16,4 @@ export * from './EntityListDocsGrid'; export * from './DocsCardGrid'; +export * from './InfoCardGrid'; diff --git a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx index 56d086acfc..5024eab5c3 100644 --- a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx +++ b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx @@ -26,8 +26,9 @@ import { } from '@backstage/plugin-catalog-react'; import { Entity } from '@backstage/catalog-model'; import { DocsTable } from './Tables'; -import { DocsCardGrid } from './Grids'; +import { DocsCardGrid, InfoCardGird } from './Grids'; import { TechDocsPageWrapper } from './TechDocsPageWrapper'; +import { TechDocsIndexPage } from './TechDocsIndexPage'; import { CodeSnippet, @@ -40,10 +41,13 @@ import { } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; import { TECHDOCS_ANNOTATION } from '@backstage/plugin-techdocs-common'; +import { EntityFilterQuery } from '@backstage/catalog-client'; const panels = { DocsTable: DocsTable, DocsCardGrid: DocsCardGrid, + TechDocsIndexPage: TechDocsIndexPage, + InfoCardGird: InfoCardGird, }; /** @@ -51,7 +55,11 @@ const panels = { * * @public */ -export type PanelType = 'DocsCardGrid' | 'DocsTable'; +export type PanelType = + | 'DocsCardGrid' + | 'DocsTable' + | 'TechDocsIndexPage' + | 'InfoCardGird'; /** * Type representing a TechDocsCustomHome panel. @@ -64,6 +72,7 @@ export interface PanelConfig { panelType: PanelType; panelCSS?: CSSProperties; filterPredicate: ((entity: Entity) => boolean) | string; + panelProps?: Record; } /** @@ -119,15 +128,21 @@ const CustomPanel = ({ return ( <> - - {index === 0 ? ( - - Discover documentation in your ecosystem. - - ) : null} - + {config.panelProps?.showHeader !== false && ( + + {index === 0 && config.panelProps?.hideSupport !== true && ( + + Discover documentation in your ecosystem. + + )} + + )}
- +
); @@ -140,10 +155,14 @@ const CustomPanel = ({ */ export type TechDocsCustomHomeProps = { tabsConfig: TabsConfig; + filter?: EntityFilterQuery; + title?: string; + subtitle?: string; + hideSubtitle?: boolean; }; export const TechDocsCustomHome = (props: TechDocsCustomHomeProps) => { - const { tabsConfig } = props; + const { tabsConfig, filter, title, subtitle, hideSubtitle } = props; const [selectedTab, setSelectedTab] = useState(0); const catalogApi: CatalogApi = useApi(catalogApiRef); @@ -153,7 +172,7 @@ export const TechDocsCustomHome = (props: TechDocsCustomHomeProps) => { error, } = useAsync(async () => { const response = await catalogApi.getEntities({ - filter: { + filter: filter || { [`metadata.annotations.${TECHDOCS_ANNOTATION}`]: CATALOG_FILTER_EXISTS, }, fields: [ @@ -165,16 +184,18 @@ export const TechDocsCustomHome = (props: TechDocsCustomHomeProps) => { 'spec.type', ], }); - return response.items.filter((entity: Entity) => { - return !!entity.metadata.annotations?.[TECHDOCS_ANNOTATION]; - }); + return response.items; }); const currentTabConfig = tabsConfig[selectedTab]; if (loading) { return ( - + @@ -184,7 +205,11 @@ export const TechDocsCustomHome = (props: TechDocsCustomHomeProps) => { if (error) { return ( - + { } return ( - + setSelectedTab(index)} diff --git a/plugins/techdocs/src/home/components/TechDocsIndexPage.tsx b/plugins/techdocs/src/home/components/TechDocsIndexPage.tsx index adbb68d3e9..baf50b89f3 100644 --- a/plugins/techdocs/src/home/components/TechDocsIndexPage.tsx +++ b/plugins/techdocs/src/home/components/TechDocsIndexPage.tsx @@ -16,7 +16,11 @@ import React from 'react'; import { useOutlet } from 'react-router-dom'; -import { TableColumn, TableProps } from '@backstage/core-components'; +import { + TableColumn, + TableProps, + TableOptions, +} from '@backstage/core-components'; import { EntityOwnerPickerProps, UserListFilterKind, @@ -34,6 +38,11 @@ export type TechDocsIndexPageProps = { columns?: TableColumn[]; actions?: TableProps['actions']; ownerPickerMode?: EntityOwnerPickerProps['mode']; + showHeader?: boolean; + hideSupport?: boolean; + options?: TableOptions; + title?: string; + subtitle?: string; }; export const TechDocsIndexPage = (props: TechDocsIndexPageProps) => { diff --git a/plugins/techdocs/src/home/components/TechDocsPageWrapper.tsx b/plugins/techdocs/src/home/components/TechDocsPageWrapper.tsx index a6192c13ee..eedac5ee95 100644 --- a/plugins/techdocs/src/home/components/TechDocsPageWrapper.tsx +++ b/plugins/techdocs/src/home/components/TechDocsPageWrapper.tsx @@ -26,6 +26,9 @@ import { useApi, configApiRef } from '@backstage/core-plugin-api'; */ export type TechDocsPageWrapperProps = { children?: React.ReactNode; + title?: string; + subtitle?: string; + hideSubtitle?: boolean; }; /** @@ -34,16 +37,18 @@ export type TechDocsPageWrapperProps = { * @public */ export const TechDocsPageWrapper = (props: TechDocsPageWrapperProps) => { - const { children } = props; + const { children, title, subtitle, hideSubtitle } = props; const configApi = useApi(configApiRef); - const generatedSubtitle = `Documentation available in ${ - configApi.getOptionalString('organization.name') ?? 'Backstage' - }`; + const generatedSubtitle = + subtitle || + `Documentation available in ${ + configApi.getOptionalString('organization.name') ?? 'Backstage' + }`; return ( {children} diff --git a/plugins/techdocs/src/overridableComponents.ts b/plugins/techdocs/src/overridableComponents.ts new file mode 100644 index 0000000000..8b371d8798 --- /dev/null +++ b/plugins/techdocs/src/overridableComponents.ts @@ -0,0 +1,36 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Overrides } from '@material-ui/core/styles/overrides'; +import { StyleRules } from '@material-ui/core/styles/withStyles'; +import { InfoCardGridClassKey } from './home/components/Grids/InfoCardGrid'; + +/** @public */ +export type CatalogReactComponentsNameToClassKey = { + BackstageInfoCardGrid: InfoCardGridClassKey; +}; + +/** @public */ +export type BackstageOverrides = Overrides & { + [Name in keyof CatalogReactComponentsNameToClassKey]?: Partial< + StyleRules + >; +}; + +declare module '@backstage/theme' { + interface OverrideComponentNameToClassKeys + extends CatalogReactComponentsNameToClassKey {} +} diff --git a/yarn.lock b/yarn.lock index 1e2c2e6742..d172cfc75e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8472,6 +8472,7 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/plugin-techdocs@workspace:plugins/techdocs" dependencies: + "@backstage/catalog-client": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" From b6a8c56a8161ea9aba865ef67c76be1090e210e7 Mon Sep 17 00:00:00 2001 From: nikolar Date: Mon, 2 Dec 2024 20:09:30 -0800 Subject: [PATCH 003/420] add testing Signed-off-by: nikolar --- .../components/Grids/InfoCardGrid.test.tsx | 191 ++++++++++++++++++ .../home/components/Grids/InfoCardGrid.tsx | 39 ++-- .../components/TechDocsCustomHome.test.tsx | 129 ++++++++++++ .../home/components/TechDocsCustomHome.tsx | 13 +- 4 files changed, 349 insertions(+), 23 deletions(-) create mode 100644 plugins/techdocs/src/home/components/Grids/InfoCardGrid.test.tsx diff --git a/plugins/techdocs/src/home/components/Grids/InfoCardGrid.test.tsx b/plugins/techdocs/src/home/components/Grids/InfoCardGrid.test.tsx new file mode 100644 index 0000000000..dcb773120e --- /dev/null +++ b/plugins/techdocs/src/home/components/Grids/InfoCardGrid.test.tsx @@ -0,0 +1,191 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { renderInTestApp } from '@backstage/test-utils'; +import { screen } from '@testing-library/react'; +import React from 'react'; +import { rootDocsRouteRef } from '../../../routes'; +import { InfoCardGrid } from './InfoCardGrid'; + +describe('Entity Info Card Grid', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('should render multiple entities', async () => { + await renderInTestApp( + , + { + mountedRoutes: { + '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, + }, + }, + ); + + expect(await screen.findByText('TestTitle')).toBeInTheDocument(); + expect(await screen.findByText('TestTitle2')).toBeInTheDocument(); + }); + + it('should handle missing data gracefully', async () => { + await renderInTestApp( + , + { + mountedRoutes: { + '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, + }, + }, + ); + + expect(await screen.findByText('testName')).toBeInTheDocument(); + }); + + it('should render links correctly', async () => { + await renderInTestApp( + , + { + mountedRoutes: { + '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, + }, + }, + ); + + expect(await screen.findByText('TestTitle')).toBeInTheDocument(); + expect(await screen.findByText('TestTitle2')).toBeInTheDocument(); + const [button1, button2] = await screen.findAllByTestId('read-docs-link'); + expect(button1.getAttribute('href')).toContain( + '/docs/default/testkind/testname', + ); + expect(button2.getAttribute('href')).toContain( + '/docs/default/testkind2/testname2', + ); + }); + + it('should render entity title if available', async () => { + await renderInTestApp( + , + { + mountedRoutes: { + '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, + }, + }, + ); + + expect(await screen.findByText('TestTitle')).toBeInTheDocument(); + }); + + it('should render entity name if title is not available', async () => { + await renderInTestApp( + , + { + mountedRoutes: { + '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, + }, + }, + ); + + expect(await screen.findByText('testName')).toBeInTheDocument(); + }); +}); diff --git a/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx b/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx index 46293f9228..7a96c2af4c 100644 --- a/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx +++ b/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx @@ -38,11 +38,11 @@ const useStyles = makeStyles( ); /** - * Props for {@link InfoCardGird} + * Props for {@link InfoCardGrid} * * @public */ -export type InfoCardGirdProps = { +export type InfoCardGridProps = { entities: Entity[] | undefined; linkContent?: string | JSX.Element; linkDest?: (entity: Entity) => string; @@ -53,37 +53,40 @@ export type InfoCardGirdProps = { * * @public */ -export const InfoCardGird = (props: InfoCardGirdProps) => { +export const InfoCardGrid = (props: InfoCardGridProps) => { const { entities, linkContent, linkDest } = props; const classes = useStyles(); const getRouteToReaderPageFor = useRouteRef(rootDocsRouteRef); const config = useApi(configApiRef); + const linkDestination = (entity: Entity) => + typeof linkDest === 'function' + ? linkDest(entity) + : getRouteToReaderPageFor({ + namespace: toLowerMaybe( + entity.metadata.namespace ?? 'default', + config, + ), + kind: toLowerMaybe(entity.kind, config), + name: toLowerMaybe(entity.metadata.name, config), + }); + if (!entities) return null; return ( - + {!entities?.length ? null : entities.map(entity => (
{entity?.metadata?.description}
{linkContent || 'Read Docs'} diff --git a/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx b/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx index 409d68a593..66e01ce780 100644 --- a/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx +++ b/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx @@ -96,4 +96,133 @@ describe('TechDocsCustomHome', () => { await screen.findByText('Second Tab Description'), ).toBeInTheDocument(); }); + it('should render ContentHeader based on showHeader prop', async () => { + const tabsConfig = [ + { + label: 'First Tab', + panels: [ + { + title: 'First Tab', + description: 'First Tab Description', + panelType: 'DocsCardGrid' as PanelType, + panelProps: { showHeader: false }, + filterPredicate: () => true, + }, + ], + }, + ]; + + await renderInTestApp( + + + , + { + mountedRoutes: { + '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, + }, + }, + ); + + expect( + screen.queryByText('Discover documentation in your ecosystem.'), + ).not.toBeInTheDocument(); + }); + it('should render SupportButton based on hideSupport prop', async () => { + const tabsConfig = [ + { + label: 'First Tab', + panels: [ + { + title: 'First Tab', + description: 'First Tab Description', + panelType: 'DocsCardGrid' as PanelType, + filterPredicate: () => true, + panelProps: { hideSupport: true }, + }, + ], + }, + ]; + + await renderInTestApp( + + + , + { + mountedRoutes: { + '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, + }, + }, + ); + + expect( + screen.queryByText('Discover documentation in your ecosystem.'), + ).not.toBeInTheDocument(); + }); + it('should hide subtitle when hideSubtitle is true', async () => { + const tabsConfig = [ + { + label: 'First Tab', + panels: [ + { + title: 'First Tab', + description: 'First Tab Description', + panelType: 'DocsCardGrid' as PanelType, + filterPredicate: () => true, + }, + ], + }, + ]; + + await renderInTestApp( + + + , + { + mountedRoutes: { + '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, + }, + }, + ); + + expect(screen.getByText('Custom Title')).toBeInTheDocument(); + expect(screen.queryByText('Custom Subtitle')).not.toBeInTheDocument(); + }); + it('should render title and subtitle', async () => { + const tabsConfig = [ + { + label: 'First Tab', + panels: [ + { + title: 'First Tab', + description: 'First Tab Description', + panelType: 'DocsCardGrid' as PanelType, + filterPredicate: () => true, + }, + ], + }, + ]; + + await renderInTestApp( + + + , + { + mountedRoutes: { + '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, + }, + }, + ); + + expect(screen.getByText('Custom Title')).toBeInTheDocument(); + expect(screen.getByText('Custom Subtitle')).toBeInTheDocument(); + }); }); diff --git a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx index 5024eab5c3..781d4b337c 100644 --- a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx +++ b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx @@ -26,7 +26,7 @@ import { } from '@backstage/plugin-catalog-react'; import { Entity } from '@backstage/catalog-model'; import { DocsTable } from './Tables'; -import { DocsCardGrid, InfoCardGird } from './Grids'; +import { DocsCardGrid, InfoCardGrid } from './Grids'; import { TechDocsPageWrapper } from './TechDocsPageWrapper'; import { TechDocsIndexPage } from './TechDocsIndexPage'; @@ -47,7 +47,7 @@ const panels = { DocsTable: DocsTable, DocsCardGrid: DocsCardGrid, TechDocsIndexPage: TechDocsIndexPage, - InfoCardGird: InfoCardGird, + InfoCardGrid: InfoCardGrid, }; /** @@ -59,7 +59,7 @@ export type PanelType = | 'DocsCardGrid' | 'DocsTable' | 'TechDocsIndexPage' - | 'InfoCardGird'; + | 'InfoCardGrid'; /** * Type representing a TechDocsCustomHome panel. @@ -172,7 +172,8 @@ export const TechDocsCustomHome = (props: TechDocsCustomHomeProps) => { error, } = useAsync(async () => { const response = await catalogApi.getEntities({ - filter: filter || { + filter: { + ...filter, [`metadata.annotations.${TECHDOCS_ANNOTATION}`]: CATALOG_FILTER_EXISTS, }, fields: [ @@ -184,7 +185,9 @@ export const TechDocsCustomHome = (props: TechDocsCustomHomeProps) => { 'spec.type', ], }); - return response.items; + return response.items.filter((entity: Entity) => { + return !!entity.metadata.annotations?.[TECHDOCS_ANNOTATION]; + }); }); const currentTabConfig = tabsConfig[selectedTab]; From 998a8062d6c13c8e31a605adf0c4cc1fae649600 Mon Sep 17 00:00:00 2001 From: nikolar Date: Mon, 2 Dec 2024 20:58:01 -0800 Subject: [PATCH 004/420] add api report Signed-off-by: nikolar --- plugins/techdocs/report.api.md | 36 +++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/plugins/techdocs/report.api.md b/plugins/techdocs/report.api.md index c58a946287..04fd8dda39 100644 --- a/plugins/techdocs/report.api.md +++ b/plugins/techdocs/report.api.md @@ -12,6 +12,7 @@ import { Config } from '@backstage/config'; import { CSSProperties } from '@material-ui/styles/withStyles'; import { DiscoveryApi } from '@backstage/core-plugin-api'; import { Entity } from '@backstage/catalog-model'; +import { EntityFilterQuery } from '@backstage/catalog-client'; import { EntityOwnerPickerProps } from '@backstage/plugin-catalog-react'; import { FetchApi } from '@backstage/core-plugin-api'; import { IdentityApi } from '@backstage/core-plugin-api'; @@ -194,6 +195,21 @@ export const EntityTechdocsContent: (props: { children?: ReactNode; }) => JSX_2.Element; +// @public +export const InfoCardGrid: ( + props: InfoCardGridProps, +) => React_2.JSX.Element | null; + +// @public (undocumented) +export type InfoCardGridClassKey = 'linkSpacer' | 'readMoreLink'; + +// @public +export type InfoCardGridProps = { + entities: Entity[] | undefined; + linkContent?: string | JSX.Element; + linkDest?: (entity: Entity) => string; +}; + // @public export const isTechDocsAvailable: (entity: Entity) => boolean; @@ -206,13 +222,19 @@ export interface PanelConfig { // (undocumented) panelCSS?: CSSProperties; // (undocumented) + panelProps?: Record; + // (undocumented) panelType: PanelType; // (undocumented) title: string; } // @public -export type PanelType = 'DocsCardGrid' | 'DocsTable'; +export type PanelType = + | 'DocsCardGrid' + | 'DocsTable' + | 'TechDocsIndexPage' + | 'InfoCardGrid'; // @public @deprecated export const Reader: ( @@ -293,6 +315,10 @@ export const TechDocsCustomHome: ( // @public export type TechDocsCustomHomeProps = { tabsConfig: TabsConfig; + filter?: EntityFilterQuery; + title?: string; + subtitle?: string; + hideSubtitle?: boolean; }; // @public @deprecated (undocumented) @@ -309,6 +335,11 @@ export type TechDocsIndexPageProps = { columns?: TableColumn[]; actions?: TableProps['actions']; ownerPickerMode?: EntityOwnerPickerProps['mode']; + showHeader?: boolean; + hideSupport?: boolean; + options?: TableOptions; + title?: string; + subtitle?: string; }; // @public @deprecated (undocumented) @@ -325,6 +356,9 @@ export const TechDocsPageWrapper: ( // @public export type TechDocsPageWrapperProps = { children?: React_2.ReactNode; + title?: string; + subtitle?: string; + hideSubtitle?: boolean; }; // @public From 1f40e6bf88379a5a8b0f529d1a65a6e5588c33d7 Mon Sep 17 00:00:00 2001 From: nikolar Date: Mon, 2 Dec 2024 22:35:58 -0800 Subject: [PATCH 005/420] add docs and clean up Signed-off-by: nikolar --- .changeset/warm-masks-ring.md | 85 +++++++++++++++++++ docs/features/techdocs/how-to-guides.md | 41 ++++++++- plugins/techdocs/report.api.md | 13 ++- .../home/components/Grids/InfoCardGrid.tsx | 12 +-- .../home/components/TechDocsCustomHome.tsx | 9 +- plugins/techdocs/src/home/components/index.ts | 1 + 6 files changed, 151 insertions(+), 10 deletions(-) create mode 100644 .changeset/warm-masks-ring.md diff --git a/.changeset/warm-masks-ring.md b/.changeset/warm-masks-ring.md new file mode 100644 index 0000000000..9d1d5e0c5a --- /dev/null +++ b/.changeset/warm-masks-ring.md @@ -0,0 +1,85 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Add optional props to `TechDocCustomHome` to allow for more flexibility: + +```tsx +import { TechDocsCustomHome } from '@backstage/plugin-techdocs'; +//... + +const options = { emptyRowsWhenPaging: false }; +const linkDestination = (entity: Entity): string | undefined => { + return entity.metadata.annotations?.['external-docs']; +}; +const techDocsTabsConfig = [ + { + label: 'Recommended Documentation', + panels: [ + { + title: 'Golden Path', + description: 'Documentation about standards to follow', + panelType: 'DocsCardGrid', + panelProps: { showHeader: false, hideSupport: true }, + filterPredicate: entity => + entity?.metadata?.tags?.includes('golden-path') ?? false, + }, + { + title: 'Recommended', + description: 'Useful documentation', + panelType: 'InfoCardGrid', + panelProps: { + showHeader: false, + hideSupport: true, + linkDestination: linkDestination, + }, + filterPredicate: entity => + entity?.metadata?.tags?.includes('recommended') ?? false, + }, + ], + }, + { + label: 'Browse All', + panels: [ + { + description: 'Browse all docs', + filterPredicate: filterEntity, + panelType: 'TechDocsIndexPage', + title: 'All', + panelProps: { showHeader: false, hideSupport: true, options: options }, + }, + ], + }, +]; + +const AppRoutes = () => { + + + } + /> + ; +}; +``` + +Add new Grid option called `InfoCardGrid` which is a more customizable card option for the Docs grid. + +```tsx + entity.metadata['external-docs']} +/> +``` + +Expose existing `CustomDocsPanel` so that it can be used independently if desired. diff --git a/docs/features/techdocs/how-to-guides.md b/docs/features/techdocs/how-to-guides.md index f8be6c9dd5..6bb4383674 100644 --- a/docs/features/techdocs/how-to-guides.md +++ b/docs/features/techdocs/how-to-guides.md @@ -137,6 +137,10 @@ Modify your `App.tsx` as follows: import { TechDocsCustomHome } from '@backstage/plugin-techdocs'; //... +const options = { emptyRowsWhenPaging: false }; +const linkDestination = (entity: Entity): string | undefined => { + return entity.metadata.annotations?.['external-docs']; +}; const techDocsTabsConfig = [ { label: 'Recommended Documentation', @@ -145,18 +149,53 @@ const techDocsTabsConfig = [ title: 'Golden Path', description: 'Documentation about standards to follow', panelType: 'DocsCardGrid', + panelProps: { showHeader: false, hideSupport: true }, + filterPredicate: entity => + entity?.metadata?.tags?.includes('golden-path') ?? false, + }, + { + title: 'Recommended', + description: 'Useful documentation', + panelType: 'InfoCardGrid', + panelProps: { + showHeader: false, + hideSupport: true, + linkDestination: linkDestination, + }, filterPredicate: entity => entity?.metadata?.tags?.includes('recommended') ?? false, }, ], }, + { + label: 'Browse All', + panels: [ + { + description: 'Browse all docs', + filterPredicate: filterEntity, + panelType: 'TechDocsIndexPage', + title: 'All', + panelProps: { showHeader: false, hideSupport: true, options: options }, + }, + ], + }, ]; const AppRoutes = () => { } + element={ + + } /> ; }; diff --git a/plugins/techdocs/report.api.md b/plugins/techdocs/report.api.md index 04fd8dda39..ea7a1d7ea1 100644 --- a/plugins/techdocs/report.api.md +++ b/plugins/techdocs/report.api.md @@ -52,6 +52,17 @@ export type ContentStateTypes = /** There is only the latest and greatest content */ | 'CONTENT_FRESH'; +// @public +export const CustomDocsPanel: ({ + config, + entities, + index, +}: { + config: PanelConfig; + entities: Entity[]; + index: number; +}) => React_2.JSX.Element; + // @public export const DefaultTechDocsHome: ( props: TechDocsIndexPageProps, @@ -207,7 +218,7 @@ export type InfoCardGridClassKey = 'linkSpacer' | 'readMoreLink'; export type InfoCardGridProps = { entities: Entity[] | undefined; linkContent?: string | JSX.Element; - linkDest?: (entity: Entity) => string; + linkDestination?: (entity: Entity) => string; }; // @public diff --git a/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx b/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx index 7a96c2af4c..3503194bc4 100644 --- a/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx +++ b/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx @@ -45,7 +45,7 @@ const useStyles = makeStyles( export type InfoCardGridProps = { entities: Entity[] | undefined; linkContent?: string | JSX.Element; - linkDest?: (entity: Entity) => string; + linkDestination?: (entity: Entity) => string; }; /** @@ -54,13 +54,13 @@ export type InfoCardGridProps = { * @public */ export const InfoCardGrid = (props: InfoCardGridProps) => { - const { entities, linkContent, linkDest } = props; + const { entities, linkContent, linkDestination } = props; const classes = useStyles(); const getRouteToReaderPageFor = useRouteRef(rootDocsRouteRef); const config = useApi(configApiRef); - const linkDestination = (entity: Entity) => - typeof linkDest === 'function' - ? linkDest(entity) + const linkRoute = (entity: Entity) => + typeof linkDestination === 'function' + ? linkDestination(entity) : getRouteToReaderPageFor({ namespace: toLowerMaybe( entity.metadata.namespace ?? 'default', @@ -84,7 +84,7 @@ export const InfoCardGrid = (props: InfoCardGridProps) => {
{entity?.metadata?.description}
diff --git a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx index 781d4b337c..b939d75893 100644 --- a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx +++ b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx @@ -92,7 +92,12 @@ export interface TabConfig { */ export type TabsConfig = TabConfig[]; -const CustomPanel = ({ +/** + * Component which can be used to render entities in a custom way. + * + * @public + */ +export const CustomDocsPanel = ({ config, entities, index, @@ -241,7 +246,7 @@ export const TechDocsCustomHome = (props: TechDocsCustomHomeProps) => { /> {currentTabConfig.panels.map((config, index) => ( - Date: Tue, 3 Dec 2024 11:03:01 -0800 Subject: [PATCH 006/420] add more detail to changeset Signed-off-by: nikolar --- .changeset/warm-masks-ring.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/.changeset/warm-masks-ring.md b/.changeset/warm-masks-ring.md index 9d1d5e0c5a..783f49474e 100644 --- a/.changeset/warm-masks-ring.md +++ b/.changeset/warm-masks-ring.md @@ -83,3 +83,35 @@ Add new Grid option called `InfoCardGrid` which is a more customizable card opti ``` Expose existing `CustomDocsPanel` so that it can be used independently if desired. + +```tsx +const panels: PanelConfig[] = [ + { + description: '', + filterPredicate: entity => {}, + panelType: 'InfoCardGrid', + title: 'Standards', + panelProps: { + hideSupport: true, + linkContent: 'Read more', + linkDestination: entity => {}, + }, + }, + { + description: '', + filterPredicate: entity => {}, + panelType: 'DocsCardGrid', + title: 'Contribute', + }, +]; +{ + panels.map((config, index) => ( + + )); +} +``` From 351952cb932b37b303362afd9efce092c652ce5a Mon Sep 17 00:00:00 2001 From: nikolar Date: Tue, 3 Dec 2024 15:41:04 -0800 Subject: [PATCH 007/420] renaming Signed-off-by: nikolar --- .changeset/warm-masks-ring.md | 10 +++---- docs/features/techdocs/how-to-guides.md | 8 ++--- plugins/techdocs/report.api.md | 10 ++++--- .../home/components/DefaultTechDocsHome.tsx | 8 ++--- .../components/TechDocsCustomHome.test.tsx | 8 ++--- .../home/components/TechDocsCustomHome.tsx | 30 ++++++++++++++----- .../src/home/components/TechDocsIndexPage.tsx | 2 +- .../home/components/TechDocsPageWrapper.tsx | 6 ++-- 8 files changed, 49 insertions(+), 33 deletions(-) diff --git a/.changeset/warm-masks-ring.md b/.changeset/warm-masks-ring.md index 783f49474e..aefbcf8013 100644 --- a/.changeset/warm-masks-ring.md +++ b/.changeset/warm-masks-ring.md @@ -20,7 +20,7 @@ const techDocsTabsConfig = [ title: 'Golden Path', description: 'Documentation about standards to follow', panelType: 'DocsCardGrid', - panelProps: { showHeader: false, hideSupport: true }, + panelProps: { showHeader: false, showSupport: false }, filterPredicate: entity => entity?.metadata?.tags?.includes('golden-path') ?? false, }, @@ -30,7 +30,7 @@ const techDocsTabsConfig = [ panelType: 'InfoCardGrid', panelProps: { showHeader: false, - hideSupport: true, + showSupport: false, linkDestination: linkDestination, }, filterPredicate: entity => @@ -46,7 +46,7 @@ const techDocsTabsConfig = [ filterPredicate: filterEntity, panelType: 'TechDocsIndexPage', title: 'All', - panelProps: { showHeader: false, hideSupport: true, options: options }, + panelProps: { showHeader: false, showSupport: false, options: options }, }, ], }, @@ -60,7 +60,7 @@ const AppRoutes = () => { {}, }, diff --git a/docs/features/techdocs/how-to-guides.md b/docs/features/techdocs/how-to-guides.md index 6bb4383674..22f48ccee1 100644 --- a/docs/features/techdocs/how-to-guides.md +++ b/docs/features/techdocs/how-to-guides.md @@ -149,7 +149,7 @@ const techDocsTabsConfig = [ title: 'Golden Path', description: 'Documentation about standards to follow', panelType: 'DocsCardGrid', - panelProps: { showHeader: false, hideSupport: true }, + panelProps: { showHeader: false, showSupport: false }, filterPredicate: entity => entity?.metadata?.tags?.includes('golden-path') ?? false, }, @@ -159,7 +159,7 @@ const techDocsTabsConfig = [ panelType: 'InfoCardGrid', panelProps: { showHeader: false, - hideSupport: true, + showSupport: false, linkDestination: linkDestination, }, filterPredicate: entity => @@ -175,7 +175,7 @@ const techDocsTabsConfig = [ filterPredicate: filterEntity, panelType: 'TechDocsIndexPage', title: 'All', - panelProps: { showHeader: false, hideSupport: true, options: options }, + panelProps: { showHeader: false, showSupport: false, options: options }, }, ], }, @@ -189,7 +189,7 @@ const AppRoutes = () => { boolean) | string; // (undocumented) panelCSS?: CSSProperties; + // Warning: (ae-forgotten-export) The symbol "PanelProps" needs to be exported by the entry point index.d.ts + // // (undocumented) - panelProps?: Record; + panelProps?: PanelProps; // (undocumented) panelType: PanelType; // (undocumented) @@ -329,7 +331,7 @@ export type TechDocsCustomHomeProps = { filter?: EntityFilterQuery; title?: string; subtitle?: string; - hideSubtitle?: boolean; + showSubtitle?: boolean; }; // @public @deprecated (undocumented) @@ -347,7 +349,7 @@ export type TechDocsIndexPageProps = { actions?: TableProps['actions']; ownerPickerMode?: EntityOwnerPickerProps['mode']; showHeader?: boolean; - hideSupport?: boolean; + showSupport?: boolean; options?: TableOptions; title?: string; subtitle?: string; @@ -369,7 +371,7 @@ export type TechDocsPageWrapperProps = { children?: React_2.ReactNode; title?: string; subtitle?: string; - hideSubtitle?: boolean; + showSubtitle?: boolean; }; // @public diff --git a/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx b/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx index f4a4e7862a..b3dbf08faa 100644 --- a/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx +++ b/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx @@ -51,17 +51,17 @@ export const DefaultTechDocsHome = (props: TechDocsIndexPageProps) => { columns, actions, ownerPickerMode, - showHeader, + showHeader = true, options, title, subtitle, - hideSupport, + showSupport = true, } = props; - const Wrapper = showHeader !== false ? TechDocsPageWrapper : React.Fragment; + const Wrapper = showHeader ? TechDocsPageWrapper : React.Fragment; return ( - {hideSupport !== true && ( + {showSupport && ( Discover documentation in your ecosystem. diff --git a/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx b/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx index 66e01ce780..3056f4448c 100644 --- a/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx +++ b/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx @@ -127,7 +127,7 @@ describe('TechDocsCustomHome', () => { screen.queryByText('Discover documentation in your ecosystem.'), ).not.toBeInTheDocument(); }); - it('should render SupportButton based on hideSupport prop', async () => { + it('should render SupportButton based on showSupport prop', async () => { const tabsConfig = [ { label: 'First Tab', @@ -137,7 +137,7 @@ describe('TechDocsCustomHome', () => { description: 'First Tab Description', panelType: 'DocsCardGrid' as PanelType, filterPredicate: () => true, - panelProps: { hideSupport: true }, + panelProps: { showSupport: false }, }, ], }, @@ -158,7 +158,7 @@ describe('TechDocsCustomHome', () => { screen.queryByText('Discover documentation in your ecosystem.'), ).not.toBeInTheDocument(); }); - it('should hide subtitle when hideSubtitle is true', async () => { + it('should hide subtitle when showSubtitle is false', async () => { const tabsConfig = [ { label: 'First Tab', @@ -179,7 +179,7 @@ describe('TechDocsCustomHome', () => { tabsConfig={tabsConfig} title="Custom Title" subtitle="Custom Subtitle" - hideSubtitle + showSubtitle={false} /> , { diff --git a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx index b939d75893..5067dd07cd 100644 --- a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx +++ b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx @@ -25,7 +25,7 @@ import { useEntityOwnership, } from '@backstage/plugin-catalog-react'; import { Entity } from '@backstage/catalog-model'; -import { DocsTable } from './Tables'; +import { DocsTable, DocsTableRow } from './Tables'; import { DocsCardGrid, InfoCardGrid } from './Grids'; import { TechDocsPageWrapper } from './TechDocsPageWrapper'; import { TechDocsIndexPage } from './TechDocsIndexPage'; @@ -38,6 +38,7 @@ import { WarningPanel, SupportButton, ContentHeader, + TableOptions, } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; import { TECHDOCS_ANNOTATION } from '@backstage/plugin-techdocs-common'; @@ -61,6 +62,19 @@ export type PanelType = | 'TechDocsIndexPage' | 'InfoCardGrid'; +/** + * Type representing Panel props + * + * @public + */ +export interface PanelProps { + showHeader?: boolean; + showSupport?: boolean; + options?: TableOptions; + linkContent?: string | JSX.Element; + linkDestination?: (entity: Entity) => string; +} + /** * Type representing a TechDocsCustomHome panel. * @@ -72,7 +86,7 @@ export interface PanelConfig { panelType: PanelType; panelCSS?: CSSProperties; filterPredicate: ((entity: Entity) => boolean) | string; - panelProps?: Record; + panelProps?: PanelProps; } /** @@ -135,7 +149,7 @@ export const CustomDocsPanel = ({ <> {config.panelProps?.showHeader !== false && ( - {index === 0 && config.panelProps?.hideSupport !== true && ( + {index === 0 && config.panelProps?.showSupport !== false && ( Discover documentation in your ecosystem. @@ -163,11 +177,11 @@ export type TechDocsCustomHomeProps = { filter?: EntityFilterQuery; title?: string; subtitle?: string; - hideSubtitle?: boolean; + showSubtitle?: boolean; }; export const TechDocsCustomHome = (props: TechDocsCustomHomeProps) => { - const { tabsConfig, filter, title, subtitle, hideSubtitle } = props; + const { tabsConfig, filter, title, subtitle, showSubtitle = true } = props; const [selectedTab, setSelectedTab] = useState(0); const catalogApi: CatalogApi = useApi(catalogApiRef); @@ -202,7 +216,7 @@ export const TechDocsCustomHome = (props: TechDocsCustomHomeProps) => { @@ -216,7 +230,7 @@ export const TechDocsCustomHome = (props: TechDocsCustomHomeProps) => { { ['actions']; ownerPickerMode?: EntityOwnerPickerProps['mode']; showHeader?: boolean; - hideSupport?: boolean; + showSupport?: boolean; options?: TableOptions; title?: string; subtitle?: string; diff --git a/plugins/techdocs/src/home/components/TechDocsPageWrapper.tsx b/plugins/techdocs/src/home/components/TechDocsPageWrapper.tsx index eedac5ee95..84f8eee327 100644 --- a/plugins/techdocs/src/home/components/TechDocsPageWrapper.tsx +++ b/plugins/techdocs/src/home/components/TechDocsPageWrapper.tsx @@ -28,7 +28,7 @@ export type TechDocsPageWrapperProps = { children?: React.ReactNode; title?: string; subtitle?: string; - hideSubtitle?: boolean; + showSubtitle?: boolean; }; /** @@ -37,7 +37,7 @@ export type TechDocsPageWrapperProps = { * @public */ export const TechDocsPageWrapper = (props: TechDocsPageWrapperProps) => { - const { children, title, subtitle, hideSubtitle } = props; + const { children, title, subtitle, showSubtitle = true } = props; const configApi = useApi(configApiRef); const generatedSubtitle = subtitle || @@ -48,7 +48,7 @@ export const TechDocsPageWrapper = (props: TechDocsPageWrapperProps) => { return ( {children} From 0d7097b705a24613a361e76fb87a91da61adedf7 Mon Sep 17 00:00:00 2001 From: nikolar Date: Tue, 3 Dec 2024 16:29:04 -0800 Subject: [PATCH 008/420] fix api-report warning Signed-off-by: nikolar --- plugins/techdocs/report.api.md | 16 ++++++++++++++-- plugins/techdocs/src/home/components/index.ts | 1 + 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/plugins/techdocs/report.api.md b/plugins/techdocs/report.api.md index ec99ddfa31..3f9920d270 100644 --- a/plugins/techdocs/report.api.md +++ b/plugins/techdocs/report.api.md @@ -232,8 +232,6 @@ export interface PanelConfig { filterPredicate: ((entity: Entity) => boolean) | string; // (undocumented) panelCSS?: CSSProperties; - // Warning: (ae-forgotten-export) The symbol "PanelProps" needs to be exported by the entry point index.d.ts - // // (undocumented) panelProps?: PanelProps; // (undocumented) @@ -242,6 +240,20 @@ export interface PanelConfig { title: string; } +// @public +export interface PanelProps { + // (undocumented) + linkContent?: string | JSX.Element; + // (undocumented) + linkDestination?: (entity: Entity) => string; + // (undocumented) + options?: TableOptions; + // (undocumented) + showHeader?: boolean; + // (undocumented) + showSupport?: boolean; +} + // @public export type PanelType = | 'DocsCardGrid' diff --git a/plugins/techdocs/src/home/components/index.ts b/plugins/techdocs/src/home/components/index.ts index 2d37ab9013..9c625817aa 100644 --- a/plugins/techdocs/src/home/components/index.ts +++ b/plugins/techdocs/src/home/components/index.ts @@ -20,6 +20,7 @@ export * from './DefaultTechDocsHome'; export type { PanelType, PanelConfig, + PanelProps, TabConfig, TabsConfig, TechDocsCustomHomeProps, From d3dc0ec77d03bccdf2e53cf1a720218dc1cb4c5d Mon Sep 17 00:00:00 2001 From: nikolar Date: Tue, 10 Dec 2024 14:19:43 -0800 Subject: [PATCH 009/420] linkDestination can be undefined Signed-off-by: nikolar --- .../home/components/Grids/InfoCardGrid.tsx | 26 ++++++++++--------- .../home/components/TechDocsCustomHome.tsx | 2 +- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx b/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx index 3503194bc4..31435debf7 100644 --- a/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx +++ b/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx @@ -45,7 +45,7 @@ const useStyles = makeStyles( export type InfoCardGridProps = { entities: Entity[] | undefined; linkContent?: string | JSX.Element; - linkDestination?: (entity: Entity) => string; + linkDestination?: (entity: Entity) => string | undefined; }; /** @@ -58,17 +58,19 @@ export const InfoCardGrid = (props: InfoCardGridProps) => { const classes = useStyles(); const getRouteToReaderPageFor = useRouteRef(rootDocsRouteRef); const config = useApi(configApiRef); - const linkRoute = (entity: Entity) => - typeof linkDestination === 'function' - ? linkDestination(entity) - : getRouteToReaderPageFor({ - namespace: toLowerMaybe( - entity.metadata.namespace ?? 'default', - config, - ), - kind: toLowerMaybe(entity.kind, config), - name: toLowerMaybe(entity.metadata.name, config), - }); + const linkRoute = (entity: Entity) => { + if (linkDestination) { + const destination = linkDestination(entity); + if (destination) { + return destination; + } + } + return getRouteToReaderPageFor({ + namespace: toLowerMaybe(entity.metadata.namespace ?? 'default', config), + kind: toLowerMaybe(entity.kind, config), + name: toLowerMaybe(entity.metadata.name, config), + }); + }; if (!entities) return null; return ( diff --git a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx index 5067dd07cd..2712667eae 100644 --- a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx +++ b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx @@ -72,7 +72,7 @@ export interface PanelProps { showSupport?: boolean; options?: TableOptions; linkContent?: string | JSX.Element; - linkDestination?: (entity: Entity) => string; + linkDestination?: (entity: Entity) => string | undefined; } /** From 74314c8999f6e7b7bb0ec019bcd79d315ec5db9c Mon Sep 17 00:00:00 2001 From: nikolar Date: Tue, 10 Dec 2024 17:01:38 -0800 Subject: [PATCH 010/420] fix api report Signed-off-by: nikolar --- plugins/techdocs/report.api.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/techdocs/report.api.md b/plugins/techdocs/report.api.md index d4e7750435..073d843872 100644 --- a/plugins/techdocs/report.api.md +++ b/plugins/techdocs/report.api.md @@ -13,7 +13,8 @@ import { CSSProperties } from '@material-ui/styles/withStyles'; import { DiscoveryApi } from '@backstage/core-plugin-api'; import { Entity } from '@backstage/catalog-model'; import { EntityFilterQuery } from '@backstage/catalog-client'; -import { EntityListPagination, EntityOwnerPickerProps } from '@backstage/plugin-catalog-react'; +import { EntityListPagination } from '@backstage/plugin-catalog-react'; +import { EntityOwnerPickerProps } from '@backstage/plugin-catalog-react'; import { FetchApi } from '@backstage/core-plugin-api'; import { IdentityApi } from '@backstage/core-plugin-api'; import { JSX as JSX_2 } from 'react'; @@ -218,7 +219,7 @@ export type InfoCardGridClassKey = 'linkSpacer' | 'readMoreLink'; export type InfoCardGridProps = { entities: Entity[] | undefined; linkContent?: string | JSX.Element; - linkDestination?: (entity: Entity) => string; + linkDestination?: (entity: Entity) => string | undefined; }; // @public @@ -245,7 +246,7 @@ export interface PanelProps { // (undocumented) linkContent?: string | JSX.Element; // (undocumented) - linkDestination?: (entity: Entity) => string; + linkDestination?: (entity: Entity) => string | undefined; // (undocumented) options?: TableOptions; // (undocumented) From 4dc54873e29a455784f601a16c200d32b13c395a Mon Sep 17 00:00:00 2001 From: Jonathan Roebuck Date: Fri, 25 Oct 2024 15:41:59 +0100 Subject: [PATCH 011/420] Add new core component Autocomplete and use for entity pickers Signed-off-by: Jonathan Roebuck --- packages/core-components/report.api.md | 14 ++ .../Autocomplete/Autocomplete.stories.tsx | 33 ++++ .../Autocomplete/Autocomplete.test.tsx | 117 ++++++++++++ .../components/Autocomplete/Autocomplete.tsx | 171 +++++++++++++++++ .../src/components/Autocomplete/index.tsx | 16 ++ .../core-components/src/components/index.ts | 1 + plugins/catalog-react/report.api.md | 2 +- .../EntityAutocompletePicker.tsx | 60 +++--- .../EntityAutocompletePickerInput.tsx | 43 ----- .../EntityOwnerPicker/EntityOwnerPicker.tsx | 172 ++++++++---------- .../EntityProcessingStatusPicker.tsx | 90 ++++----- 11 files changed, 479 insertions(+), 240 deletions(-) create mode 100644 packages/core-components/src/components/Autocomplete/Autocomplete.stories.tsx create mode 100644 packages/core-components/src/components/Autocomplete/Autocomplete.test.tsx create mode 100644 packages/core-components/src/components/Autocomplete/Autocomplete.tsx create mode 100644 packages/core-components/src/components/Autocomplete/index.tsx delete mode 100644 plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePickerInput.tsx diff --git a/packages/core-components/report.api.md b/packages/core-components/report.api.md index c3f7cf9a6a..5298ac9816 100644 --- a/packages/core-components/report.api.md +++ b/packages/core-components/report.api.md @@ -6,6 +6,7 @@ /// import { ApiRef } from '@backstage/core-plugin-api'; +import { AutocompleteProps } from '@material-ui/lab/Autocomplete'; import { BackstageIdentityApi } from '@backstage/core-plugin-api'; import { BackstagePalette } from '@backstage/theme'; import { BackstageUserIdentity } from '@backstage/core-plugin-api'; @@ -33,6 +34,7 @@ import { MaterialTableProps } from '@material-table/core'; import { NavLinkProps } from 'react-router-dom'; import { Options } from 'react-markdown'; import { Options as Options_2 } from '@material-table/core'; +import { OutlinedTextFieldProps } from '@material-ui/core/TextField'; import { Overrides } from '@material-ui/core/styles/overrides'; import { ProfileInfo } from '@backstage/core-plugin-api'; import { ProfileInfoApi } from '@backstage/core-plugin-api'; @@ -76,6 +78,18 @@ export type AppIconProps = IconComponentProps & { Fallback?: IconComponent; }; +// Warning: (ae-forgotten-export) The symbol "AutocompleteComponentProps" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export function Autocomplete< + T, + Multiple extends boolean | undefined = undefined, + DisableClearable extends boolean | undefined = undefined, + FreeSolo extends boolean | undefined = undefined, +>( + props: AutocompleteComponentProps, +): React_2.JSX.Element; + // @public export const AutoLogout: (props: AutoLogoutProps) => JSX.Element | null; diff --git a/packages/core-components/src/components/Autocomplete/Autocomplete.stories.tsx b/packages/core-components/src/components/Autocomplete/Autocomplete.stories.tsx new file mode 100644 index 0000000000..7aed709125 --- /dev/null +++ b/packages/core-components/src/components/Autocomplete/Autocomplete.stories.tsx @@ -0,0 +1,33 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { AutocompleteComponent as Autocomplete } from './Autocomplete'; + +export default { + title: 'Inputs/Autocomplete', + component: Autocomplete, +}; + +export const Default = (args: any) => { + return ; +}; + +Default.args = { + multiple: true, + label: 'Default', + name: 'default', + options: ['test 1', 'test 2', 'test 3'], +}; diff --git a/packages/core-components/src/components/Autocomplete/Autocomplete.test.tsx b/packages/core-components/src/components/Autocomplete/Autocomplete.test.tsx new file mode 100644 index 0000000000..fff83eb26d --- /dev/null +++ b/packages/core-components/src/components/Autocomplete/Autocomplete.test.tsx @@ -0,0 +1,117 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { AutocompleteComponent as Autocomplete } from './Autocomplete'; + +describe('Autocomplete', () => { + const user = userEvent.setup(); + const mockOptions = ['Option 1', 'Option 2', 'Option 3']; + + it('renders without exploding', () => { + render( + , + ); + expect(screen.getByRole('textbox')).toBeInTheDocument(); + }); + + it('renders the expand icon', () => { + render( + , + ); + const expandIcon = screen.getByTestId('test-autocomplete-expand'); + expect(expandIcon).toBeInTheDocument(); + }); + + it('displays options when clicked', () => { + render( + , + ); + + const input = screen.getByRole('textbox'); + user.click(input); + + mockOptions.forEach(option => { + expect(screen.getByText(option)).toBeInTheDocument(); + }); + }); + + it('supports required input', () => { + render( + , + ); + + const input = screen.getByRole('textbox'); + expect(input).toBeRequired(); + }); + + it('displays helper text when provided', () => { + render( + , + ); + + expect(screen.getByText('Helper text')).toBeInTheDocument(); + }); + + it('renders without label', () => { + render(); + + const input = screen.getByRole('textbox'); + expect(input).toBeInTheDocument(); + }); + + it('displays correct option on selection', () => { + render( + , + ); + + const input = screen.getByRole('textbox'); + user.click(input); + + const optionToSelect = screen.getByText('Option 1'); + user.click(optionToSelect); + + expect(input).toHaveValue('Option 1'); + }); +}); diff --git a/packages/core-components/src/components/Autocomplete/Autocomplete.tsx b/packages/core-components/src/components/Autocomplete/Autocomplete.tsx new file mode 100644 index 0000000000..dd0c936c14 --- /dev/null +++ b/packages/core-components/src/components/Autocomplete/Autocomplete.tsx @@ -0,0 +1,171 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Box from '@material-ui/core/Box'; +import Typography from '@material-ui/core/Typography'; +import Paper, { PaperProps } from '@material-ui/core/Paper'; +import Popper, { PopperProps } from '@material-ui/core/Popper'; +import TextField, { OutlinedTextFieldProps } from '@material-ui/core/TextField'; +import Grow from '@material-ui/core/Grow'; +import { + createStyles, + makeStyles, + Theme, + withStyles, +} from '@material-ui/core/styles'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import Autocomplete, { AutocompleteProps } from '@material-ui/lab/Autocomplete'; +import React, { ReactNode } from 'react'; + +const useStyles = makeStyles( + theme => ({ + root: {}, + label: { + position: 'relative', + fontWeight: 'bold', + fontSize: theme.typography.body2.fontSize, + fontFamily: theme.typography.fontFamily, + color: theme.palette.text.primary, + '& > span': { + top: 0, + left: 0, + position: 'absolute', + }, + }, + input: {}, + }), + { name: 'BackstageAutocomplete' }, +); + +const BootstrapAutocomplete = withStyles( + (theme: Theme) => + createStyles({ + root: {}, + paper: { + margin: 0, + }, + hasClearIcon: {}, + hasPopupIcon: {}, + focused: {}, + inputRoot: { + marginTop: 24, + backgroundColor: theme.palette.background.paper, + '$root$hasClearIcon$hasPopupIcon &': { + padding: `${theme.spacing(0.75, 7, 0.75, 1.5)}`, + }, + '$root$focused &': { + outline: 'none', + }, + '$root &:hover > fieldset': { + borderColor: '#ced4da', + }, + '$root$focused & > fieldset': { + borderWidth: 1, + borderColor: theme.palette.primary.main, + }, + }, + popupIndicator: { + padding: 0, + margin: 0, + color: theme.palette.text.primary, + '& [class*="MuiTouchRipple-root"]': { + display: 'none', + }, + }, + endAdornment: { + '$root$hasClearIcon$hasPopupIcon &': { + right: 4, + }, + }, + input: { + '$root$hasClearIcon$hasPopupIcon &': { + height: 32, + fontSize: theme.typography.body1.fontSize, + padding: 0, + }, + }, + }), + { name: 'BackstageAutocompleteBase' }, +)(Autocomplete) as typeof Autocomplete; + +const PopperComponent = (props: PopperProps) => ( + + {({ TransitionProps }) => ( + + {props.children as ReactNode} + + )} + +); + +const PaperComponent = (props: PaperProps) => ( + +); + +export type AutocompleteComponentProps< + T, + Multiple extends boolean | undefined = undefined, + DisableClearable extends boolean | undefined = undefined, + FreeSolo extends boolean | undefined = undefined, +> = { + name: string; + label?: string; + inputProps?: Omit; +} & Omit< + AutocompleteProps, + 'PopperComponent' | 'PaperComponent' | 'renderInput' | 'size' | 'popupIcon' +>; + +/** @public */ +export function AutocompleteComponent< + T, + Multiple extends boolean | undefined = undefined, + DisableClearable extends boolean | undefined = undefined, + FreeSolo extends boolean | undefined = undefined, +>(props: AutocompleteComponentProps) { + const { label, name, inputProps, ...rest } = props; + const classes = useStyles(); + const autocomplete = ( + } + PaperComponent={PaperComponent} + PopperComponent={PopperComponent} + renderInput={params => ( + + )} + /> + ); + + return ( + + {label ? ( + + {label} + {autocomplete} + + ) : ( + autocomplete + )} + + ); +} diff --git a/packages/core-components/src/components/Autocomplete/index.tsx b/packages/core-components/src/components/Autocomplete/index.tsx new file mode 100644 index 0000000000..bf4681db8e --- /dev/null +++ b/packages/core-components/src/components/Autocomplete/index.tsx @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { AutocompleteComponent as Autocomplete } from './Autocomplete'; diff --git a/packages/core-components/src/components/index.ts b/packages/core-components/src/components/index.ts index 828df3ec03..6fcbe2e9a9 100644 --- a/packages/core-components/src/components/index.ts +++ b/packages/core-components/src/components/index.ts @@ -16,6 +16,7 @@ export * from './AlertDisplay'; export * from './AutoLogout'; +export * from './Autocomplete'; export * from './Avatar'; export * from './LinkButton'; export * from './CodeSnippet'; diff --git a/plugins/catalog-react/report.api.md b/plugins/catalog-react/report.api.md index af5102176d..107fbeb0c0 100644 --- a/plugins/catalog-react/report.api.md +++ b/plugins/catalog-react/report.api.md @@ -205,7 +205,7 @@ export type EntityAutocompletePickerProps< Filter: { new (values: string[]): NonNullable; }; - InputProps?: TextFieldProps; + InputProps?: TextFieldProps['InputProps']; initialSelectedOptions?: string[]; filtersForAvailableValues?: Array; }; diff --git a/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.tsx b/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.tsx index fd88a7a31e..d4f978fce5 100644 --- a/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.tsx +++ b/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.tsx @@ -16,21 +16,18 @@ import Box from '@material-ui/core/Box'; import { TextFieldProps } from '@material-ui/core/TextField'; -import Typography from '@material-ui/core/Typography'; import { makeStyles } from '@material-ui/core/styles'; -import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; -import Autocomplete from '@material-ui/lab/Autocomplete'; -import React, { useEffect, useMemo, useState, ReactNode } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { useApi } from '@backstage/core-plugin-api'; import useAsync from 'react-use/esm/useAsync'; import { catalogApiRef } from '../../api'; import { EntityAutocompletePickerOption } from './EntityAutocompletePickerOption'; -import { EntityAutocompletePickerInput } from './EntityAutocompletePickerInput'; import { DefaultEntityFilters, useEntityList, } from '../../hooks/useEntityListProvider'; import { EntityFilter } from '../../types'; +import { Autocomplete } from '@backstage/core-components'; import { reduceBackendCatalogFilters } from '../../utils/filters'; /** @public */ @@ -52,7 +49,7 @@ export type EntityAutocompletePickerProps< path: string; showCounts?: boolean; Filter: { new (values: string[]): NonNullable }; - InputProps?: TextFieldProps; + InputProps?: TextFieldProps['InputProps']; initialSelectedOptions?: string[]; filtersForAvailableValues?: Array; }; @@ -82,11 +79,9 @@ export function EntityAutocompletePicker< path, showCounts, Filter, - InputProps, initialSelectedOptions = [], filtersForAvailableValues = ['kind'], } = props; - const classes = useStyles(); const { @@ -152,36 +147,25 @@ export function EntityAutocompletePicker< return ( - - {label} - - PopperComponent={popperProps => ( -
{popperProps.children as ReactNode}
- )} - multiple - disableCloseOnSelect - options={availableOptions} - value={selectedOptions} - onChange={(_event: object, options: string[]) => - setSelectedOptions(options) - } - renderOption={(option, { selected }) => ( - - )} - size="small" - popupIcon={ - - } - renderInput={params => ( - - )} - /> -
+ + multiple + disableCloseOnSelect + label={label} + name={`${String(name)}-picker`} + options={availableOptions} + value={selectedOptions} + onChange={(_event: object, options: string[]) => + setSelectedOptions(options) + } + renderOption={(option, { selected }) => ( + + )} + />
); } diff --git a/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePickerInput.tsx b/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePickerInput.tsx deleted file mode 100644 index 133024330c..0000000000 --- a/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePickerInput.tsx +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import TextField, { TextFieldProps } from '@material-ui/core/TextField'; -import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'; -import React from 'react'; -import classnames from 'classnames'; - -const useStyles = makeStyles( - (theme: Theme) => - createStyles({ - input: { - backgroundColor: theme.palette.background.paper, - }, - }), - { - name: 'CatalogReactEntityAutocompletePickerInput', - }, -); - -export function EntityAutocompletePickerInput(params: TextFieldProps) { - const classes = useStyles(); - - return ( - - ); -} diff --git a/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx b/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx index ecbdae1d63..bf8db5f627 100644 --- a/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx +++ b/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx @@ -22,15 +22,13 @@ import { import Box from '@material-ui/core/Box'; import Checkbox from '@material-ui/core/Checkbox'; import FormControlLabel from '@material-ui/core/FormControlLabel'; -import TextField from '@material-ui/core/TextField'; import Typography from '@material-ui/core/Typography'; import Tooltip from '@material-ui/core/Tooltip'; -import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import CheckBoxIcon from '@material-ui/icons/CheckBox'; import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank'; -import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; -import Autocomplete from '@material-ui/lab/Autocomplete'; -import React, { useEffect, useMemo, useState, ReactNode } from 'react'; +import { Autocomplete } from '@backstage/core-components'; +import React, { useEffect, useMemo, useState } from 'react'; import { useEntityList } from '../../hooks/useEntityListProvider'; import { EntityOwnerFilter } from '../../filters'; import { useDebouncedEffect } from '@react-hookz/web'; @@ -42,29 +40,20 @@ import { withStyles } from '@material-ui/core/styles'; import { useEntityPresentation } from '../../apis'; import { catalogReactTranslationRef } from '../../translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; -import { PopperProps } from '@material-ui/core/Popper'; /** @public */ export type CatalogReactEntityOwnerPickerClassKey = 'input'; const useStyles = makeStyles( - (theme: Theme) => - createStyles({ - root: {}, - label: { - textTransform: 'none', - fontWeight: 'bold', - }, - input: { - backgroundColor: theme.palette.background.paper, - }, - fullWidth: { width: '100%' }, - boxLabel: { - width: '100%', - textOverflow: 'ellipsis', - overflow: 'hidden', - }, - }), + { + root: {}, + fullWidth: { width: '100%' }, + boxLabel: { + width: '100%', + textOverflow: 'ellipsis', + overflow: 'hidden', + }, + }, { name: 'CatalogReactEntityOwnerPicker' }, ); @@ -147,7 +136,7 @@ export const EntityOwnerPicker = (props?: EntityOwnerPickerProps) => { [ownersParameter], ); - const [selectedOwners, setSelectedOwners] = useState( + const [selectedOwners, setSelectedOwners] = useState( queryParamOwners.length ? queryParamOwners : filters.owners?.values ?? [], ); @@ -186,84 +175,67 @@ export const EntityOwnerPicker = (props?: EntityOwnerPickerProps) => { return ( - - {t('entityOwnerPicker.title')} - { - if (typeof v === 'string') { - return stringifyEntityRef(o) === v; - } - return o === v; - }} - getOptionLabel={o => { - const entity = - typeof o === 'string' - ? cache.getEntity(o) || - parseEntityRef(o, { - defaultKind: 'group', - defaultNamespace: 'default', - }) - : o; - return humanizeEntity(entity, humanizeEntityRef(entity)); - }} - onChange={(_: object, owners) => { - setText(''); - setSelectedOwners( - owners.map(e => { - const entityRef = - typeof e === 'string' ? e : stringifyEntityRef(e); + + label={t('entityOwnerPicker.title')} + multiple + disableCloseOnSelect + loading={loading} + options={availableOwners} + value={selectedOwners as unknown as Entity[]} + getOptionSelected={(o, v) => { + if (typeof v === 'string') { + return stringifyEntityRef(o) === v; + } + return o === v; + }} + getOptionLabel={o => { + const entity = + typeof o === 'string' + ? cache.getEntity(o) || + parseEntityRef(o, { + defaultKind: 'group', + defaultNamespace: 'default', + }) + : o; + return humanizeEntity(entity, humanizeEntityRef(entity)); + }} + onChange={(_: object, owners) => { + setText(''); + setSelectedOwners( + owners.map(e => { + const entityRef = + typeof e === 'string' ? e : stringifyEntityRef(e); - if (typeof e !== 'string') { - cache.setEntity(e); - } - return entityRef; - }), - ); - }} - filterOptions={x => x} - renderOption={(entity, { selected }) => { - return ; - }} - size="small" - popupIcon={} - renderInput={params => ( - { - setText(e.currentTarget.value); - }} - variant="outlined" - /> - )} - ListboxProps={{ - onScroll: (e: React.MouseEvent) => { - const element = e.currentTarget; - const hasReachedEnd = - Math.abs( - element.scrollHeight - - element.clientHeight - - element.scrollTop, - ) < 1; - - if (hasReachedEnd && value?.cursor) { - handleFetch({ items: value.items, cursor: value.cursor }); + if (typeof e !== 'string') { + cache.setEntity(e); } - }, - 'data-testid': 'owner-picker-listbox', - }} - /> - + return entityRef; + }), + ); + }} + filterOptions={x => x} + renderOption={(entity, { selected }) => { + return ; + }} + name="owner-picker" + onInputChange={(_e, inputValue) => { + setText(inputValue); + }} + ListboxProps={{ + onScroll: (e: React.MouseEvent) => { + const element = e.currentTarget; + const hasReachedEnd = + Math.abs( + element.scrollHeight - element.clientHeight - element.scrollTop, + ) < 1; + + if (hasReachedEnd && value?.cursor) { + handleFetch({ items: value.items, cursor: value.cursor }); + } + }, + 'data-testid': 'owner-picker-listbox', + }} + /> ); }; - -function Popper({ children }: PopperProps) { - return
{children as ReactNode}
; -} diff --git a/plugins/catalog-react/src/components/EntityProcessingStatusPicker/EntityProcessingStatusPicker.tsx b/plugins/catalog-react/src/components/EntityProcessingStatusPicker/EntityProcessingStatusPicker.tsx index a2f0c8f729..02d1ddc238 100644 --- a/plugins/catalog-react/src/components/EntityProcessingStatusPicker/EntityProcessingStatusPicker.tsx +++ b/plugins/catalog-react/src/components/EntityProcessingStatusPicker/EntityProcessingStatusPicker.tsx @@ -18,15 +18,12 @@ import { EntityErrorFilter, EntityOrphanFilter } from '../../filters'; import Box from '@material-ui/core/Box'; import Checkbox from '@material-ui/core/Checkbox'; import FormControlLabel from '@material-ui/core/FormControlLabel'; -import TextField from '@material-ui/core/TextField'; -import Typography from '@material-ui/core/Typography'; -import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'; +import { makeStyles } from '@material-ui/core/styles'; import CheckBoxIcon from '@material-ui/icons/CheckBox'; import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank'; -import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; -import React, { useState, ReactNode } from 'react'; +import React, { useState } from 'react'; import { useEntityList } from '../../hooks'; -import Autocomplete from '@material-ui/lab/Autocomplete'; +import { Autocomplete } from '@backstage/core-components'; import { catalogReactTranslationRef } from '../../translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; @@ -34,17 +31,9 @@ import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; export type CatalogReactEntityProcessingStatusPickerClassKey = 'input'; const useStyles = makeStyles( - (theme: Theme) => - createStyles({ - root: {}, - input: { - backgroundColor: theme.palette.background.paper, - }, - label: { - textTransform: 'none', - fontWeight: 'bold', - }, - }), + { + root: {}, + }, { name: 'CatalogReactEntityProcessingStatusPickerPicker' }, ); @@ -77,47 +66,32 @@ export const EntityProcessingStatusPicker = () => { return ( - - {t('entityProcessingStatusPicker.title')} - - PopperComponent={popperProps => ( -
{popperProps.children as ReactNode}
- )} - multiple - disableCloseOnSelect - options={availableAdvancedItems} - value={selectedAdvancedItems} - onChange={(_: object, value: string[]) => { - setSelectedAdvancedItems(value); - orphanChange(value.includes('Is Orphan')); - errorChange(value.includes('Has Error')); - }} - renderOption={(option, { selected }) => ( - - } - onClick={event => event.preventDefault()} - label={option} - /> - )} - size="small" - popupIcon={ - - } - renderInput={params => ( - - )} - /> -
+ + label={t('entityProcessingStatusPicker.title')} + multiple + disableCloseOnSelect + options={availableAdvancedItems} + value={selectedAdvancedItems} + onChange={(_: object, value: string[]) => { + setSelectedAdvancedItems(value); + orphanChange(value.includes('Is Orphan')); + errorChange(value.includes('Has Error')); + }} + renderOption={(option, { selected }) => ( + + } + onClick={event => event.preventDefault()} + label={option} + /> + )} + name="processing-status-picker" + />
); }; From b9ad22a0cfffef9d573efa2fa3f652a7909210ce Mon Sep 17 00:00:00 2001 From: Jonathan Roebuck Date: Mon, 4 Nov 2024 16:16:17 +0000 Subject: [PATCH 012/420] update styles. make renderInput optional Signed-off-by: Jonathan Roebuck --- .../components/Autocomplete/Autocomplete.tsx | 43 ++++++++++++------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/packages/core-components/src/components/Autocomplete/Autocomplete.tsx b/packages/core-components/src/components/Autocomplete/Autocomplete.tsx index dd0c936c14..f0016be430 100644 --- a/packages/core-components/src/components/Autocomplete/Autocomplete.tsx +++ b/packages/core-components/src/components/Autocomplete/Autocomplete.tsx @@ -27,8 +27,11 @@ import { withStyles, } from '@material-ui/core/styles'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; -import Autocomplete, { AutocompleteProps } from '@material-ui/lab/Autocomplete'; -import React, { ReactNode } from 'react'; +import Autocomplete, { + AutocompleteProps, + AutocompleteRenderInputParams, +} from '@material-ui/lab/Autocomplete'; +import React, { ReactNode, useCallback } from 'react'; const useStyles = makeStyles( theme => ({ @@ -64,7 +67,8 @@ const BootstrapAutocomplete = withStyles( marginTop: 24, backgroundColor: theme.palette.background.paper, '$root$hasClearIcon$hasPopupIcon &': { - padding: `${theme.spacing(0.75, 7, 0.75, 1.5)}`, + paddingBlock: theme.spacing(1.5625), + paddingInlineStart: theme.spacing(1.5), }, '$root$focused &': { outline: 'none', @@ -80,7 +84,7 @@ const BootstrapAutocomplete = withStyles( popupIndicator: { padding: 0, margin: 0, - color: theme.palette.text.primary, + color: '#616161', '& [class*="MuiTouchRipple-root"]': { display: 'none', }, @@ -92,7 +96,6 @@ const BootstrapAutocomplete = withStyles( }, input: { '$root$hasClearIcon$hasPopupIcon &': { - height: 32, fontSize: theme.typography.body1.fontSize, padding: 0, }, @@ -124,9 +127,15 @@ export type AutocompleteComponentProps< name: string; label?: string; inputProps?: Omit; + renderInput?: AutocompleteProps< + T, + Multiple, + DisableClearable, + FreeSolo + >['renderInput']; } & Omit< AutocompleteProps, - 'PopperComponent' | 'PaperComponent' | 'renderInput' | 'size' | 'popupIcon' + 'PopperComponent' | 'PaperComponent' | 'popupIcon' >; /** @public */ @@ -138,21 +147,25 @@ export function AutocompleteComponent< >(props: AutocompleteComponentProps) { const { label, name, inputProps, ...rest } = props; const classes = useStyles(); + const renderInput = useCallback( + (params: AutocompleteRenderInputParams) => ( + + ), + [], + ); const autocomplete = ( } PaperComponent={PaperComponent} PopperComponent={PopperComponent} - renderInput={params => ( - - )} /> ); From 71a05f932e32844e0d3b3c5aa30c5e6241c9b43d Mon Sep 17 00:00:00 2001 From: Jonathan Roebuck Date: Tue, 5 Nov 2024 09:19:20 +0000 Subject: [PATCH 013/420] prevent input classnames breaking change Signed-off-by: Jonathan Roebuck --- packages/core-components/report.api.md | 23 ++++++++++- .../Autocomplete/Autocomplete.test.tsx | 4 +- .../components/Autocomplete/Autocomplete.tsx | 39 ++++++++++--------- .../src/components/Autocomplete/index.tsx | 5 ++- plugins/catalog-react/report.api.md | 2 +- .../EntityAutocompletePicker.tsx | 4 +- 6 files changed, 51 insertions(+), 26 deletions(-) diff --git a/packages/core-components/report.api.md b/packages/core-components/report.api.md index 5298ac9816..5a9f5440ec 100644 --- a/packages/core-components/report.api.md +++ b/packages/core-components/report.api.md @@ -78,8 +78,6 @@ export type AppIconProps = IconComponentProps & { Fallback?: IconComponent; }; -// Warning: (ae-forgotten-export) The symbol "AutocompleteComponentProps" needs to be exported by the entry point index.d.ts -// // @public (undocumented) export function Autocomplete< T, @@ -90,6 +88,27 @@ export function Autocomplete< props: AutocompleteComponentProps, ): React_2.JSX.Element; +// @public (undocumented) +export type AutocompleteComponentProps< + T, + Multiple extends boolean | undefined = undefined, + DisableClearable extends boolean | undefined = undefined, + FreeSolo extends boolean | undefined = undefined, +> = Omit< + AutocompleteProps, + 'PopperComponent' | 'PaperComponent' | 'popupIcon' | 'renderInput' +> & { + name: string; + label?: string; + TextFieldProps?: Omit; + renderInput?: AutocompleteProps< + T, + Multiple, + DisableClearable, + FreeSolo + >['renderInput']; +}; + // @public export const AutoLogout: (props: AutoLogoutProps) => JSX.Element | null; diff --git a/packages/core-components/src/components/Autocomplete/Autocomplete.test.tsx b/packages/core-components/src/components/Autocomplete/Autocomplete.test.tsx index fff83eb26d..445267390a 100644 --- a/packages/core-components/src/components/Autocomplete/Autocomplete.test.tsx +++ b/packages/core-components/src/components/Autocomplete/Autocomplete.test.tsx @@ -69,7 +69,7 @@ describe('Autocomplete', () => { name="test-autocomplete" options={mockOptions} label="Test Label" - inputProps={{ required: true }} + TextFieldProps={{ required: true }} />, ); @@ -83,7 +83,7 @@ describe('Autocomplete', () => { name="test-autocomplete" options={mockOptions} label="Test Label" - inputProps={{ helperText: 'Helper text' }} + TextFieldProps={{ helperText: 'Helper text' }} />, ); diff --git a/packages/core-components/src/components/Autocomplete/Autocomplete.tsx b/packages/core-components/src/components/Autocomplete/Autocomplete.tsx index f0016be430..a34dd8728a 100644 --- a/packages/core-components/src/components/Autocomplete/Autocomplete.tsx +++ b/packages/core-components/src/components/Autocomplete/Autocomplete.tsx @@ -32,10 +32,13 @@ import Autocomplete, { AutocompleteRenderInputParams, } from '@material-ui/lab/Autocomplete'; import React, { ReactNode, useCallback } from 'react'; +import { merge } from 'lodash'; const useStyles = makeStyles( theme => ({ - root: {}, + root: { + margin: theme.spacing(1, 0), + }, label: { position: 'relative', fontWeight: 'bold', @@ -48,7 +51,6 @@ const useStyles = makeStyles( position: 'absolute', }, }, - input: {}, }), { name: 'BackstageAutocomplete' }, ); @@ -67,8 +69,8 @@ const BootstrapAutocomplete = withStyles( marginTop: 24, backgroundColor: theme.palette.background.paper, '$root$hasClearIcon$hasPopupIcon &': { - paddingBlock: theme.spacing(1.5625), - paddingInlineStart: theme.spacing(1.5), + paddingBlock: theme.spacing(0.75), + paddingInlineStart: theme.spacing(0.75), }, '$root$focused &': { outline: 'none', @@ -85,6 +87,9 @@ const BootstrapAutocomplete = withStyles( padding: 0, margin: 0, color: '#616161', + '&:hover': { + backgroundColor: 'unset', + }, '& [class*="MuiTouchRipple-root"]': { display: 'none', }, @@ -97,7 +102,7 @@ const BootstrapAutocomplete = withStyles( input: { '$root$hasClearIcon$hasPopupIcon &': { fontSize: theme.typography.body1.fontSize, - padding: 0, + paddingBlock: theme.spacing(0.8125), }, }, }), @@ -118,25 +123,26 @@ const PaperComponent = (props: PaperProps) => ( ); +/** @public */ export type AutocompleteComponentProps< T, Multiple extends boolean | undefined = undefined, DisableClearable extends boolean | undefined = undefined, FreeSolo extends boolean | undefined = undefined, -> = { +> = Omit< + AutocompleteProps, + 'PopperComponent' | 'PaperComponent' | 'popupIcon' | 'renderInput' +> & { name: string; label?: string; - inputProps?: Omit; + TextFieldProps?: Omit; renderInput?: AutocompleteProps< T, Multiple, DisableClearable, FreeSolo >['renderInput']; -} & Omit< - AutocompleteProps, - 'PopperComponent' | 'PaperComponent' | 'popupIcon' ->; +}; /** @public */ export function AutocompleteComponent< @@ -145,18 +151,13 @@ export function AutocompleteComponent< DisableClearable extends boolean | undefined = undefined, FreeSolo extends boolean | undefined = undefined, >(props: AutocompleteComponentProps) { - const { label, name, inputProps, ...rest } = props; + const { label, name, TextFieldProps, ...rest } = props; const classes = useStyles(); const renderInput = useCallback( (params: AutocompleteRenderInputParams) => ( - + ), - [], + [TextFieldProps], ); const autocomplete = ( ; }; - InputProps?: TextFieldProps['InputProps']; + InputProps?: TextFieldProps; initialSelectedOptions?: string[]; filtersForAvailableValues?: Array; }; diff --git a/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.tsx b/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.tsx index d4f978fce5..3f4ec707c6 100644 --- a/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.tsx +++ b/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.tsx @@ -49,7 +49,7 @@ export type EntityAutocompletePickerProps< path: string; showCounts?: boolean; Filter: { new (values: string[]): NonNullable }; - InputProps?: TextFieldProps['InputProps']; + InputProps?: TextFieldProps; initialSelectedOptions?: string[]; filtersForAvailableValues?: Array; }; @@ -79,6 +79,7 @@ export function EntityAutocompletePicker< path, showCounts, Filter, + InputProps, initialSelectedOptions = [], filtersForAvailableValues = ['kind'], } = props; @@ -154,6 +155,7 @@ export function EntityAutocompletePicker< name={`${String(name)}-picker`} options={availableOptions} value={selectedOptions} + TextFieldProps={InputProps} onChange={(_event: object, options: string[]) => setSelectedOptions(options) } From 30021e8dfe1eba27ee29d8a936ad6176af0ca61f Mon Sep 17 00:00:00 2001 From: Jonathan Roebuck Date: Tue, 5 Nov 2024 09:47:54 +0000 Subject: [PATCH 014/420] prevent label classnames breaking change Signed-off-by: Jonathan Roebuck --- packages/core-components/report.api.md | 2 ++ .../src/components/Autocomplete/Autocomplete.tsx | 12 +++++++++--- .../EntityOwnerPicker/EntityOwnerPicker.tsx | 4 ++++ .../EntityProcessingStatusPicker.tsx | 4 ++++ 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/packages/core-components/report.api.md b/packages/core-components/report.api.md index 5a9f5440ec..b2872a086b 100644 --- a/packages/core-components/report.api.md +++ b/packages/core-components/report.api.md @@ -53,6 +53,7 @@ import { StyleRules as StyleRules_2 } from '@material-ui/core/styles/withStyles' import { TabProps } from '@material-ui/core/Tab'; import { Theme } from '@material-ui/core/styles'; import { TooltipProps } from '@material-ui/core/Tooltip'; +import { TypographyProps } from '@material-ui/core/Typography'; import { WithStyles } from '@material-ui/core/styles'; // @public @@ -100,6 +101,7 @@ export type AutocompleteComponentProps< > & { name: string; label?: string; + LabelProps?: TypographyProps<'label'>; TextFieldProps?: Omit; renderInput?: AutocompleteProps< T, diff --git a/packages/core-components/src/components/Autocomplete/Autocomplete.tsx b/packages/core-components/src/components/Autocomplete/Autocomplete.tsx index a34dd8728a..e01497c5a1 100644 --- a/packages/core-components/src/components/Autocomplete/Autocomplete.tsx +++ b/packages/core-components/src/components/Autocomplete/Autocomplete.tsx @@ -15,7 +15,7 @@ */ import Box from '@material-ui/core/Box'; -import Typography from '@material-ui/core/Typography'; +import Typography, { TypographyProps } from '@material-ui/core/Typography'; import Paper, { PaperProps } from '@material-ui/core/Paper'; import Popper, { PopperProps } from '@material-ui/core/Popper'; import TextField, { OutlinedTextFieldProps } from '@material-ui/core/TextField'; @@ -33,6 +33,7 @@ import Autocomplete, { } from '@material-ui/lab/Autocomplete'; import React, { ReactNode, useCallback } from 'react'; import { merge } from 'lodash'; +import classNames from 'classnames'; const useStyles = makeStyles( theme => ({ @@ -135,6 +136,7 @@ export type AutocompleteComponentProps< > & { name: string; label?: string; + LabelProps?: TypographyProps<'label'>; TextFieldProps?: Omit; renderInput?: AutocompleteProps< T, @@ -151,7 +153,7 @@ export function AutocompleteComponent< DisableClearable extends boolean | undefined = undefined, FreeSolo extends boolean | undefined = undefined, >(props: AutocompleteComponentProps) { - const { label, name, TextFieldProps, ...rest } = props; + const { label, name, LabelProps, TextFieldProps, ...rest } = props; const classes = useStyles(); const renderInput = useCallback( (params: AutocompleteRenderInputParams) => ( @@ -173,7 +175,11 @@ export function AutocompleteComponent< return ( {label ? ( - + {label} {autocomplete} diff --git a/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx b/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx index bf8db5f627..e26f231ff4 100644 --- a/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx +++ b/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx @@ -47,6 +47,8 @@ export type CatalogReactEntityOwnerPickerClassKey = 'input'; const useStyles = makeStyles( { root: {}, + label: {}, + input: {}, fullWidth: { width: '100%' }, boxLabel: { width: '100%', @@ -235,6 +237,8 @@ export const EntityOwnerPicker = (props?: EntityOwnerPickerProps) => { }, 'data-testid': 'owner-picker-listbox', }} + LabelProps={{ className: classes.label }} + TextFieldProps={{ className: classes.input }} /> ); diff --git a/plugins/catalog-react/src/components/EntityProcessingStatusPicker/EntityProcessingStatusPicker.tsx b/plugins/catalog-react/src/components/EntityProcessingStatusPicker/EntityProcessingStatusPicker.tsx index 02d1ddc238..891f6acc9e 100644 --- a/plugins/catalog-react/src/components/EntityProcessingStatusPicker/EntityProcessingStatusPicker.tsx +++ b/plugins/catalog-react/src/components/EntityProcessingStatusPicker/EntityProcessingStatusPicker.tsx @@ -33,6 +33,8 @@ export type CatalogReactEntityProcessingStatusPickerClassKey = 'input'; const useStyles = makeStyles( { root: {}, + input: {}, + label: {}, }, { name: 'CatalogReactEntityProcessingStatusPickerPicker' }, ); @@ -91,6 +93,8 @@ export const EntityProcessingStatusPicker = () => { /> )} name="processing-status-picker" + LabelProps={{ className: classes.label }} + TextFieldProps={{ className: classes.input }} /> ); From aaf650854bda5c60e6516a54773cd2706e2f4668 Mon Sep 17 00:00:00 2001 From: Jonathan Roebuck Date: Tue, 5 Nov 2024 10:39:23 +0000 Subject: [PATCH 015/420] add changeset Signed-off-by: Jonathan Roebuck --- .changeset/fluffy-jars-protect.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/fluffy-jars-protect.md diff --git a/.changeset/fluffy-jars-protect.md b/.changeset/fluffy-jars-protect.md new file mode 100644 index 0000000000..c4967aded2 --- /dev/null +++ b/.changeset/fluffy-jars-protect.md @@ -0,0 +1,6 @@ +--- +'@backstage/core-components': minor +'@backstage/plugin-catalog-react': patch +--- + +Uses new Autocomplete component from core-components that aligns with Select component UI for consistent a dropdown UI for all catalog filters From 9e4eb5fc554750ccac8e2f6bd5b07972c777b4c3 Mon Sep 17 00:00:00 2001 From: Jonathan Roebuck Date: Tue, 5 Nov 2024 17:48:37 +0000 Subject: [PATCH 016/420] fix tests Signed-off-by: Jonathan Roebuck --- .../src/components/Autocomplete/Autocomplete.test.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/core-components/src/components/Autocomplete/Autocomplete.test.tsx b/packages/core-components/src/components/Autocomplete/Autocomplete.test.tsx index 445267390a..12269109a4 100644 --- a/packages/core-components/src/components/Autocomplete/Autocomplete.test.tsx +++ b/packages/core-components/src/components/Autocomplete/Autocomplete.test.tsx @@ -46,7 +46,7 @@ describe('Autocomplete', () => { expect(expandIcon).toBeInTheDocument(); }); - it('displays options when clicked', () => { + it('displays options when clicked', async () => { render( { ); const input = screen.getByRole('textbox'); - user.click(input); + await user.click(input); mockOptions.forEach(option => { expect(screen.getByText(option)).toBeInTheDocument(); @@ -97,7 +97,7 @@ describe('Autocomplete', () => { expect(input).toBeInTheDocument(); }); - it('displays correct option on selection', () => { + it('displays correct option on selection', async () => { render( { ); const input = screen.getByRole('textbox'); - user.click(input); + await user.click(input); const optionToSelect = screen.getByText('Option 1'); - user.click(optionToSelect); + await user.click(optionToSelect); expect(input).toHaveValue('Option 1'); }); From 50ec481ebe219e8e70440be9235a8e0b3e3c1c53 Mon Sep 17 00:00:00 2001 From: Jonathan Roebuck Date: Mon, 18 Nov 2024 11:30:21 +0000 Subject: [PATCH 017/420] split out core component changeset Signed-off-by: Jonathan Roebuck --- .changeset/eleven-monkeys-cross.md | 5 +++++ .changeset/fluffy-jars-protect.md | 3 +-- 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 .changeset/eleven-monkeys-cross.md diff --git a/.changeset/eleven-monkeys-cross.md b/.changeset/eleven-monkeys-cross.md new file mode 100644 index 0000000000..7a6ae45619 --- /dev/null +++ b/.changeset/eleven-monkeys-cross.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': minor +--- + +Introduces a new core component, Autocomplete, which enhances the MUI Autocomplete component with custom input styling, improved popper animation, and better label positioning. This addition will standardize Autocomplete implementations across Backstage and ensure seamless integration with other core components such as Select. diff --git a/.changeset/fluffy-jars-protect.md b/.changeset/fluffy-jars-protect.md index c4967aded2..4fd6b3d194 100644 --- a/.changeset/fluffy-jars-protect.md +++ b/.changeset/fluffy-jars-protect.md @@ -1,6 +1,5 @@ --- -'@backstage/core-components': minor '@backstage/plugin-catalog-react': patch --- -Uses new Autocomplete component from core-components that aligns with Select component UI for consistent a dropdown UI for all catalog filters +Uses new Autocomplete component from core-components that aligns with Select component UI for consistent a dropdown UI for all catalog filters. From b18fe46435971f3ff412405b66a4e8589314978e Mon Sep 17 00:00:00 2001 From: Jonathan Roebuck Date: Thu, 12 Dec 2024 15:59:50 +0000 Subject: [PATCH 018/420] colocate Autocomplete component in catalog-react Signed-off-by: Jonathan Roebuck --- .../Autocomplete/Autocomplete.stories.tsx | 33 ------------------- .../core-components/src/components/index.ts | 1 - .../CatalogAutocomplete.test.tsx | 18 +++++----- .../CatalogAutocomplete.tsx | 6 ++-- .../components/CatalogAutocomplete}/index.tsx | 6 ++-- .../EntityAutocompletePicker.tsx | 4 +-- .../EntityOwnerPicker/EntityOwnerPicker.tsx | 4 +-- .../EntityProcessingStatusPicker.tsx | 4 +-- 8 files changed, 22 insertions(+), 54 deletions(-) delete mode 100644 packages/core-components/src/components/Autocomplete/Autocomplete.stories.tsx rename packages/core-components/src/components/Autocomplete/Autocomplete.test.tsx => plugins/catalog-react/src/components/CatalogAutocomplete/CatalogAutocomplete.test.tsx (89%) rename packages/core-components/src/components/Autocomplete/Autocomplete.tsx => plugins/catalog-react/src/components/CatalogAutocomplete/CatalogAutocomplete.tsx (97%) rename {packages/core-components/src/components/Autocomplete => plugins/catalog-react/src/components/CatalogAutocomplete}/index.tsx (85%) diff --git a/packages/core-components/src/components/Autocomplete/Autocomplete.stories.tsx b/packages/core-components/src/components/Autocomplete/Autocomplete.stories.tsx deleted file mode 100644 index 7aed709125..0000000000 --- a/packages/core-components/src/components/Autocomplete/Autocomplete.stories.tsx +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import React from 'react'; -import { AutocompleteComponent as Autocomplete } from './Autocomplete'; - -export default { - title: 'Inputs/Autocomplete', - component: Autocomplete, -}; - -export const Default = (args: any) => { - return ; -}; - -Default.args = { - multiple: true, - label: 'Default', - name: 'default', - options: ['test 1', 'test 2', 'test 3'], -}; diff --git a/packages/core-components/src/components/index.ts b/packages/core-components/src/components/index.ts index 6fcbe2e9a9..828df3ec03 100644 --- a/packages/core-components/src/components/index.ts +++ b/packages/core-components/src/components/index.ts @@ -16,7 +16,6 @@ export * from './AlertDisplay'; export * from './AutoLogout'; -export * from './Autocomplete'; export * from './Avatar'; export * from './LinkButton'; export * from './CodeSnippet'; diff --git a/packages/core-components/src/components/Autocomplete/Autocomplete.test.tsx b/plugins/catalog-react/src/components/CatalogAutocomplete/CatalogAutocomplete.test.tsx similarity index 89% rename from packages/core-components/src/components/Autocomplete/Autocomplete.test.tsx rename to plugins/catalog-react/src/components/CatalogAutocomplete/CatalogAutocomplete.test.tsx index 12269109a4..51d272f4f1 100644 --- a/packages/core-components/src/components/Autocomplete/Autocomplete.test.tsx +++ b/plugins/catalog-react/src/components/CatalogAutocomplete/CatalogAutocomplete.test.tsx @@ -17,7 +17,7 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { AutocompleteComponent as Autocomplete } from './Autocomplete'; +import { CatalogAutocomplete } from './CatalogAutocomplete'; describe('Autocomplete', () => { const user = userEvent.setup(); @@ -25,7 +25,7 @@ describe('Autocomplete', () => { it('renders without exploding', () => { render( - { it('renders the expand icon', () => { render( - { it('displays options when clicked', async () => { render( - { it('supports required input', () => { render( - { it('displays helper text when provided', () => { render( - { }); it('renders without label', () => { - render(); + render( + , + ); const input = screen.getByRole('textbox'); expect(input).toBeInTheDocument(); @@ -99,7 +101,7 @@ describe('Autocomplete', () => { it('displays correct option on selection', async () => { render( - ( ); /** @public */ -export type AutocompleteComponentProps< +export type CatalogAutocompleteProps< T, Multiple extends boolean | undefined = undefined, DisableClearable extends boolean | undefined = undefined, @@ -147,12 +147,12 @@ export type AutocompleteComponentProps< }; /** @public */ -export function AutocompleteComponent< +export function CatalogAutocomplete< T, Multiple extends boolean | undefined = undefined, DisableClearable extends boolean | undefined = undefined, FreeSolo extends boolean | undefined = undefined, ->(props: AutocompleteComponentProps) { +>(props: CatalogAutocompleteProps) { const { label, name, LabelProps, TextFieldProps, ...rest } = props; const classes = useStyles(); const renderInput = useCallback( diff --git a/packages/core-components/src/components/Autocomplete/index.tsx b/plugins/catalog-react/src/components/CatalogAutocomplete/index.tsx similarity index 85% rename from packages/core-components/src/components/Autocomplete/index.tsx rename to plugins/catalog-react/src/components/CatalogAutocomplete/index.tsx index 404546de1a..7f1552c5bb 100644 --- a/packages/core-components/src/components/Autocomplete/index.tsx +++ b/plugins/catalog-react/src/components/CatalogAutocomplete/index.tsx @@ -14,6 +14,6 @@ * limitations under the License. */ export { - AutocompleteComponent as Autocomplete, - type AutocompleteComponentProps, -} from './Autocomplete'; + CatalogAutocomplete, + type CatalogAutocompleteProps, +} from './CatalogAutocomplete'; diff --git a/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.tsx b/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.tsx index 3f4ec707c6..09b5c1e908 100644 --- a/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.tsx +++ b/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.tsx @@ -27,8 +27,8 @@ import { useEntityList, } from '../../hooks/useEntityListProvider'; import { EntityFilter } from '../../types'; -import { Autocomplete } from '@backstage/core-components'; import { reduceBackendCatalogFilters } from '../../utils/filters'; +import { CatalogAutocomplete } from '../CatalogAutocomplete'; /** @public */ export type AllowedEntityFilters = { @@ -148,7 +148,7 @@ export function EntityAutocompletePicker< return ( - + multiple disableCloseOnSelect label={label} diff --git a/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx b/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx index e26f231ff4..b70ec0bcb5 100644 --- a/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx +++ b/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx @@ -27,7 +27,6 @@ import Tooltip from '@material-ui/core/Tooltip'; import { makeStyles } from '@material-ui/core/styles'; import CheckBoxIcon from '@material-ui/icons/CheckBox'; import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank'; -import { Autocomplete } from '@backstage/core-components'; import React, { useEffect, useMemo, useState } from 'react'; import { useEntityList } from '../../hooks/useEntityListProvider'; import { EntityOwnerFilter } from '../../filters'; @@ -40,6 +39,7 @@ import { withStyles } from '@material-ui/core/styles'; import { useEntityPresentation } from '../../apis'; import { catalogReactTranslationRef } from '../../translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { CatalogAutocomplete } from '../CatalogAutocomplete'; /** @public */ export type CatalogReactEntityOwnerPickerClassKey = 'input'; @@ -177,7 +177,7 @@ export const EntityOwnerPicker = (props?: EntityOwnerPickerProps) => { return ( - + label={t('entityOwnerPicker.title')} multiple disableCloseOnSelect diff --git a/plugins/catalog-react/src/components/EntityProcessingStatusPicker/EntityProcessingStatusPicker.tsx b/plugins/catalog-react/src/components/EntityProcessingStatusPicker/EntityProcessingStatusPicker.tsx index 891f6acc9e..a1b6e5b562 100644 --- a/plugins/catalog-react/src/components/EntityProcessingStatusPicker/EntityProcessingStatusPicker.tsx +++ b/plugins/catalog-react/src/components/EntityProcessingStatusPicker/EntityProcessingStatusPicker.tsx @@ -23,9 +23,9 @@ import CheckBoxIcon from '@material-ui/icons/CheckBox'; import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank'; import React, { useState } from 'react'; import { useEntityList } from '../../hooks'; -import { Autocomplete } from '@backstage/core-components'; import { catalogReactTranslationRef } from '../../translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { CatalogAutocomplete } from '../CatalogAutocomplete'; /** @public */ export type CatalogReactEntityProcessingStatusPickerClassKey = 'input'; @@ -68,7 +68,7 @@ export const EntityProcessingStatusPicker = () => { return ( - + label={t('entityProcessingStatusPicker.title')} multiple disableCloseOnSelect From 4b264730f49071144129623fd5f62213d4fcd3ab Mon Sep 17 00:00:00 2001 From: Jonathan Roebuck Date: Thu, 12 Dec 2024 16:09:43 +0000 Subject: [PATCH 019/420] update changesets Signed-off-by: Jonathan Roebuck --- .changeset/eleven-monkeys-cross.md | 5 ----- .changeset/fluffy-jars-protect.md | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) delete mode 100644 .changeset/eleven-monkeys-cross.md diff --git a/.changeset/eleven-monkeys-cross.md b/.changeset/eleven-monkeys-cross.md deleted file mode 100644 index 7a6ae45619..0000000000 --- a/.changeset/eleven-monkeys-cross.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-components': minor ---- - -Introduces a new core component, Autocomplete, which enhances the MUI Autocomplete component with custom input styling, improved popper animation, and better label positioning. This addition will standardize Autocomplete implementations across Backstage and ensure seamless integration with other core components such as Select. diff --git a/.changeset/fluffy-jars-protect.md b/.changeset/fluffy-jars-protect.md index 4fd6b3d194..89ff39d9af 100644 --- a/.changeset/fluffy-jars-protect.md +++ b/.changeset/fluffy-jars-protect.md @@ -2,4 +2,4 @@ '@backstage/plugin-catalog-react': patch --- -Uses new Autocomplete component from core-components that aligns with Select component UI for consistent a dropdown UI for all catalog filters. +Creates new CatalogAutocomplete component in catalog-react that aligns with Select component UI for consistent a dropdown UI for all catalog filters. From e8778469ebe7adf04b81bd0138cfbdf90d67d645 Mon Sep 17 00:00:00 2001 From: Jonathan Roebuck Date: Thu, 12 Dec 2024 16:14:04 +0000 Subject: [PATCH 020/420] remove public comments Signed-off-by: Jonathan Roebuck --- .../components/CatalogAutocomplete/CatalogAutocomplete.test.tsx | 2 +- .../src/components/CatalogAutocomplete/CatalogAutocomplete.tsx | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/plugins/catalog-react/src/components/CatalogAutocomplete/CatalogAutocomplete.test.tsx b/plugins/catalog-react/src/components/CatalogAutocomplete/CatalogAutocomplete.test.tsx index 51d272f4f1..e574919611 100644 --- a/plugins/catalog-react/src/components/CatalogAutocomplete/CatalogAutocomplete.test.tsx +++ b/plugins/catalog-react/src/components/CatalogAutocomplete/CatalogAutocomplete.test.tsx @@ -19,7 +19,7 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { CatalogAutocomplete } from './CatalogAutocomplete'; -describe('Autocomplete', () => { +describe('CatalogAutocomplete', () => { const user = userEvent.setup(); const mockOptions = ['Option 1', 'Option 2', 'Option 3']; diff --git a/plugins/catalog-react/src/components/CatalogAutocomplete/CatalogAutocomplete.tsx b/plugins/catalog-react/src/components/CatalogAutocomplete/CatalogAutocomplete.tsx index 99df8dcad4..af41db8626 100644 --- a/plugins/catalog-react/src/components/CatalogAutocomplete/CatalogAutocomplete.tsx +++ b/plugins/catalog-react/src/components/CatalogAutocomplete/CatalogAutocomplete.tsx @@ -124,7 +124,6 @@ const PaperComponent = (props: PaperProps) => ( ); -/** @public */ export type CatalogAutocompleteProps< T, Multiple extends boolean | undefined = undefined, @@ -146,7 +145,6 @@ export type CatalogAutocompleteProps< >['renderInput']; }; -/** @public */ export function CatalogAutocomplete< T, Multiple extends boolean | undefined = undefined, From 893e92f0b8f1a9810c1f1ba2a8cc4de6b793da01 Mon Sep 17 00:00:00 2001 From: Jonathan Roebuck Date: Thu, 12 Dec 2024 16:17:24 +0000 Subject: [PATCH 021/420] rebuild api docs Signed-off-by: Jonathan Roebuck --- packages/core-components/report.api.md | 35 -------------------------- 1 file changed, 35 deletions(-) diff --git a/packages/core-components/report.api.md b/packages/core-components/report.api.md index b2872a086b..c3f7cf9a6a 100644 --- a/packages/core-components/report.api.md +++ b/packages/core-components/report.api.md @@ -6,7 +6,6 @@ /// import { ApiRef } from '@backstage/core-plugin-api'; -import { AutocompleteProps } from '@material-ui/lab/Autocomplete'; import { BackstageIdentityApi } from '@backstage/core-plugin-api'; import { BackstagePalette } from '@backstage/theme'; import { BackstageUserIdentity } from '@backstage/core-plugin-api'; @@ -34,7 +33,6 @@ import { MaterialTableProps } from '@material-table/core'; import { NavLinkProps } from 'react-router-dom'; import { Options } from 'react-markdown'; import { Options as Options_2 } from '@material-table/core'; -import { OutlinedTextFieldProps } from '@material-ui/core/TextField'; import { Overrides } from '@material-ui/core/styles/overrides'; import { ProfileInfo } from '@backstage/core-plugin-api'; import { ProfileInfoApi } from '@backstage/core-plugin-api'; @@ -53,7 +51,6 @@ import { StyleRules as StyleRules_2 } from '@material-ui/core/styles/withStyles' import { TabProps } from '@material-ui/core/Tab'; import { Theme } from '@material-ui/core/styles'; import { TooltipProps } from '@material-ui/core/Tooltip'; -import { TypographyProps } from '@material-ui/core/Typography'; import { WithStyles } from '@material-ui/core/styles'; // @public @@ -79,38 +76,6 @@ export type AppIconProps = IconComponentProps & { Fallback?: IconComponent; }; -// @public (undocumented) -export function Autocomplete< - T, - Multiple extends boolean | undefined = undefined, - DisableClearable extends boolean | undefined = undefined, - FreeSolo extends boolean | undefined = undefined, ->( - props: AutocompleteComponentProps, -): React_2.JSX.Element; - -// @public (undocumented) -export type AutocompleteComponentProps< - T, - Multiple extends boolean | undefined = undefined, - DisableClearable extends boolean | undefined = undefined, - FreeSolo extends boolean | undefined = undefined, -> = Omit< - AutocompleteProps, - 'PopperComponent' | 'PaperComponent' | 'popupIcon' | 'renderInput' -> & { - name: string; - label?: string; - LabelProps?: TypographyProps<'label'>; - TextFieldProps?: Omit; - renderInput?: AutocompleteProps< - T, - Multiple, - DisableClearable, - FreeSolo - >['renderInput']; -}; - // @public export const AutoLogout: (props: AutoLogoutProps) => JSX.Element | null; From 3e165dc19b2498c408950350cb41e7b6d79cd9e0 Mon Sep 17 00:00:00 2001 From: Ali Ok Date: Tue, 17 Dec 2024 08:30:54 +0300 Subject: [PATCH 022/420] Add Knative event mesh plugin to the plugin listing Signed-off-by: Ali Ok --- microsite/data/plugins/knative-event-mesh.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 microsite/data/plugins/knative-event-mesh.yaml diff --git a/microsite/data/plugins/knative-event-mesh.yaml b/microsite/data/plugins/knative-event-mesh.yaml new file mode 100644 index 0000000000..107114cc78 --- /dev/null +++ b/microsite/data/plugins/knative-event-mesh.yaml @@ -0,0 +1,9 @@ +--- +title: Knative Event Mesh +author: Knative Community +authorUrl: https://github.com/knative-extensions/backstage-plugins +category: Monitoring +description: A plugin that provides a way to view and manage Knative Event Mesh resources. +documentation: https://knative.dev/docs/install/installing-backstage-plugins/ +npmPackageName: '@knative-extensions/plugin-knative-event-mesh-backend' +addedDate: '2024-12-16' From edaf9258417224bd43cebb815001f9340aac40bc Mon Sep 17 00:00:00 2001 From: Jonathan Sundquist Date: Mon, 16 Dec 2024 13:41:38 -0600 Subject: [PATCH 023/420] Upates to allow users to subscribe to the newly created GitHub repo Signed-off-by: Jonathan Sundquist --- .changeset/breezy-coats-sort.md | 5 ++++ .../report.api.md | 2 ++ .../src/actions/github.test.ts | 25 +++++++++++++++++++ .../src/actions/github.ts | 4 +++ .../src/actions/githubRepoCreate.test.ts | 25 +++++++++++++++++++ .../src/actions/githubRepoCreate.ts | 4 +++ .../src/actions/helpers.ts | 10 ++++++++ .../src/actions/inputProperties.ts | 7 ++++++ 8 files changed, 82 insertions(+) create mode 100644 .changeset/breezy-coats-sort.md diff --git a/.changeset/breezy-coats-sort.md b/.changeset/breezy-coats-sort.md new file mode 100644 index 0000000000..862d60e4fc --- /dev/null +++ b/.changeset/breezy-coats-sort.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-github': patch +--- + +Updates to allow users to subscribe to the newly created repository within GitHub to mimic similar functionality found within the GitHub UI. diff --git a/plugins/scaffolder-backend-module-github/report.api.md b/plugins/scaffolder-backend-module-github/report.api.md index 60ed9db5ef..935886bf14 100644 --- a/plugins/scaffolder-backend-module-github/report.api.md +++ b/plugins/scaffolder-backend-module-github/report.api.md @@ -279,6 +279,7 @@ export function createGithubRepoCreateAction(options: { [key: string]: string; } | undefined; + subscribe?: boolean | undefined; }, JsonObject >; @@ -441,6 +442,7 @@ export function createPublishGithubAction(options: { [key: string]: string; } | undefined; + subscribe?: boolean | undefined; }, JsonObject >; diff --git a/plugins/scaffolder-backend-module-github/src/actions/github.test.ts b/plugins/scaffolder-backend-module-github/src/actions/github.test.ts index f2f36e263a..2f629622d0 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/github.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/github.test.ts @@ -74,6 +74,9 @@ const mockOctokit = { createOrUpdateRepoSecret: jest.fn(), getRepoPublicKey: jest.fn(), }, + activity: { + setRepoSubscription: jest.fn(), + }, }, request: jest.fn(), }; @@ -1796,4 +1799,26 @@ describe('publish:github', () => { }); }, ); + + it('should add user subscription', async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + subscribe: true, + }, + }); + + expect(mockOctokit.rest.activity.setRepoSubscription).toHaveBeenCalledWith({ + owner: 'owner', + repo: 'repo', + subscribed: true, + ignored: false, + }); + }); }); diff --git a/plugins/scaffolder-backend-module-github/src/actions/github.ts b/plugins/scaffolder-backend-module-github/src/actions/github.ts index 6d8e0046c2..e3a9ff3352 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/github.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/github.ts @@ -117,6 +117,7 @@ export function createPublishGithubAction(options: { requiredCommitSigning?: boolean; requiredLinearHistory?: boolean; customProperties?: { [key: string]: string }; + subscribe?: boolean; }>({ id: 'publish:github', description: @@ -168,6 +169,7 @@ export function createPublishGithubAction(options: { requiredCommitSigning: inputProps.requiredCommitSigning, requiredLinearHistory: inputProps.requiredLinearHistory, customProperties: inputProps.customProperties, + subscribe: inputProps.subscribe, }, }, output: { @@ -218,6 +220,7 @@ export function createPublishGithubAction(options: { oidcCustomization, token: providedToken, customProperties, + subscribe = false, requiredCommitSigning = false, requiredLinearHistory = false, } = ctx.input; @@ -260,6 +263,7 @@ export function createPublishGithubAction(options: { secrets, oidcCustomization, customProperties, + subscribe, ctx.logger, ); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts index 66d54366c5..864c5b729f 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts @@ -55,6 +55,9 @@ const mockOctokit = { createOrUpdateRepoSecret: jest.fn(), getRepoPublicKey: jest.fn(), }, + activity: { + setRepoSubscription: jest.fn(), + }, }, request: jest.fn(), }; @@ -754,4 +757,26 @@ describe('github:repo:create', () => { 'https://github.com/clone/url.git', ); }); + + it('should subscribe user to repository', async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + subscribe: true, + }, + }); + + expect(mockOctokit.rest.activity.setRepoSubscription).toHaveBeenCalledWith({ + owner: 'owner', + repo: 'repo', + subscribed: true, + ignored: false, + }); + }); }); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts index 61dea32c35..b4608e57de 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts @@ -102,6 +102,7 @@ export function createGithubRepoCreateAction(options: { requireCommitSigning?: boolean; requiredLinearHistory?: boolean; customProperties?: { [key: string]: string }; + subscribe?: boolean; }>({ id: 'github:repo:create', description: 'Creates a GitHub repository.', @@ -143,6 +144,7 @@ export function createGithubRepoCreateAction(options: { requiredCommitSigning: inputProps.requiredCommitSigning, requiredLinearHistory: inputProps.requiredLinearHistory, customProperties: inputProps.customProperties, + subscribe: inputProps.subscribe, }, }, output: { @@ -176,6 +178,7 @@ export function createGithubRepoCreateAction(options: { secrets, oidcCustomization, customProperties, + subscribe, token: providedToken, } = ctx.input; @@ -217,6 +220,7 @@ export function createGithubRepoCreateAction(options: { secrets, oidcCustomization, customProperties, + subscribe, ctx.logger, ); diff --git a/plugins/scaffolder-backend-module-github/src/actions/helpers.ts b/plugins/scaffolder-backend-module-github/src/actions/helpers.ts index 9bba0ca359..242f95ce34 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/helpers.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/helpers.ts @@ -149,6 +149,7 @@ export async function createGithubRepoWithCollaboratorsAndTopics( } | undefined, customProperties: { [key: string]: string } | undefined, + subscribe: boolean | undefined, logger: LoggerService, ) { // eslint-disable-next-line testing-library/no-await-sync-queries @@ -330,6 +331,15 @@ export async function createGithubRepoWithCollaboratorsAndTopics( ); } + if (subscribe) { + await client.rest.activity.setRepoSubscription({ + subscribed: true, + ignored: false, + owner, + repo, + }); + } + return newRepo; } diff --git a/plugins/scaffolder-backend-module-github/src/actions/inputProperties.ts b/plugins/scaffolder-backend-module-github/src/actions/inputProperties.ts index 2c86cf492e..8b30168225 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/inputProperties.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/inputProperties.ts @@ -317,6 +317,12 @@ const customProperties = { type: 'object', }; +const subscribe = { + title: 'Subscribe to repository', + description: `Subscribe to the repository. The default value is 'false'`, + type: 'boolean', +}; + export { access }; export { allowMergeCommit }; export { allowRebaseMerge }; @@ -357,3 +363,4 @@ export { repoVariables }; export { secrets }; export { oidcCustomization }; export { customProperties }; +export { subscribe }; From e1561c2ade32994baf2f9198f15b4756513c28fc Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Tue, 17 Dec 2024 18:22:23 +0000 Subject: [PATCH 024/420] Add Field + Input components Signed-off-by: Charles de Dreuille --- packages/canon/.storybook/preview.tsx | 1 - packages/canon/package.json | 3 +- .../src/components/Field/Field.stories.tsx | 39 +++++++++ packages/canon/src/components/Field/Field.tsx | 84 +++++++++++++++++++ .../canon/src/components/Field/styles.css | 29 +++++++ .../src/components/Input/Input.stories.tsx | 34 ++++++++ packages/canon/src/components/Input/Input.tsx | 33 ++++++++ packages/canon/src/css/components.css | 1 + yarn.lock | 1 + 9 files changed, 223 insertions(+), 2 deletions(-) create mode 100644 packages/canon/src/components/Field/Field.stories.tsx create mode 100644 packages/canon/src/components/Field/Field.tsx create mode 100644 packages/canon/src/components/Field/styles.css create mode 100644 packages/canon/src/components/Input/Input.stories.tsx create mode 100644 packages/canon/src/components/Input/Input.tsx diff --git a/packages/canon/.storybook/preview.tsx b/packages/canon/.storybook/preview.tsx index bb8bbea4ef..1ac53acd86 100644 --- a/packages/canon/.storybook/preview.tsx +++ b/packages/canon/.storybook/preview.tsx @@ -75,7 +75,6 @@ const preview: Preview = { }, }, }, - defaultViewport: 'small', }, }, decorators: [ diff --git a/packages/canon/package.json b/packages/canon/package.json index e151680d5d..f32e559263 100644 --- a/packages/canon/package.json +++ b/packages/canon/package.json @@ -38,7 +38,8 @@ }, "dependencies": { "@base-ui-components/react": "^1.0.0-alpha.4", - "@remixicon/react": "^4.5.0" + "@remixicon/react": "^4.5.0", + "clsx": "^2.1.1" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/packages/canon/src/components/Field/Field.stories.tsx b/packages/canon/src/components/Field/Field.stories.tsx new file mode 100644 index 0000000000..d6f061ac05 --- /dev/null +++ b/packages/canon/src/components/Field/Field.stories.tsx @@ -0,0 +1,39 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; +import { Field } from './Field'; +import { Input } from '../Input/Input'; +const meta = { + title: 'Components/Field', + component: Field.Root, + parameters: { + layout: 'centered', + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => ( + + Label + + + ), +}; diff --git a/packages/canon/src/components/Field/Field.tsx b/packages/canon/src/components/Field/Field.tsx new file mode 100644 index 0000000000..78cc638662 --- /dev/null +++ b/packages/canon/src/components/Field/Field.tsx @@ -0,0 +1,84 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { Field as FieldPrimitive } from '@base-ui-components/react/field'; +import clsx from 'clsx'; + +const FieldRoot = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +FieldRoot.displayName = FieldPrimitive.Root.displayName; + +const FieldLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +FieldLabel.displayName = FieldPrimitive.Label.displayName; + +const FieldDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +FieldDescription.displayName = FieldPrimitive.Description.displayName; + +const FieldError = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +FieldError.displayName = FieldPrimitive.Error.displayName; + +const FieldValidity = ({ + children, + ...props +}: React.ComponentPropsWithoutRef) => ( + + {validityState => children(validityState)} + +); + +export const Field = { + Root: FieldRoot, + Label: FieldLabel, + Description: FieldDescription, + Error: FieldError, + Validity: FieldValidity, +}; diff --git a/packages/canon/src/components/Field/styles.css b/packages/canon/src/components/Field/styles.css new file mode 100644 index 0000000000..728ee87f31 --- /dev/null +++ b/packages/canon/src/components/Field/styles.css @@ -0,0 +1,29 @@ +.canon-fieldRoot { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.canon-fieldLabel { + font-size: 0.875rem; + font-weight: 500; + line-height: 1.25rem; +} + +.canon-fieldControl { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.canon-fieldDescription { + font-size: 0.875rem; + font-weight: 400; + line-height: 1.25rem; +} + +.canon-fieldError { + font-size: 0.875rem; + font-weight: 400; + line-height: 1.25rem; +} diff --git a/packages/canon/src/components/Input/Input.stories.tsx b/packages/canon/src/components/Input/Input.stories.tsx new file mode 100644 index 0000000000..54f8a86768 --- /dev/null +++ b/packages/canon/src/components/Input/Input.stories.tsx @@ -0,0 +1,34 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; +import { Input } from './Input'; + +const meta = { + title: 'Components/Input', + component: Input, + parameters: { + layout: 'centered', + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + render: () => , +}; diff --git a/packages/canon/src/components/Input/Input.tsx b/packages/canon/src/components/Input/Input.tsx new file mode 100644 index 0000000000..c5b3aad5f5 --- /dev/null +++ b/packages/canon/src/components/Input/Input.tsx @@ -0,0 +1,33 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; + +import { Input as InputPrimitive } from '@base-ui-components/react/input'; +import clsx from 'clsx'; + +const Input = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +Input.displayName = InputPrimitive.displayName; + +export { Input }; diff --git a/packages/canon/src/css/components.css b/packages/canon/src/css/components.css index ed602b7ab7..2dc13a7677 100644 --- a/packages/canon/src/css/components.css +++ b/packages/canon/src/css/components.css @@ -25,3 +25,4 @@ @import '../components/Table/styles.css'; @import '../components/Text/styles.css'; @import '../components/Heading/styles.css'; +@import '../components/Field/styles.css'; diff --git a/yarn.lock b/yarn.lock index 1b9fd0a972..6f107b08a6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3826,6 +3826,7 @@ __metadata: "@vanilla-extract/rollup-plugin": ^1.3.10 "@vanilla-extract/sprinkles": ^1.6.3 "@vanilla-extract/webpack-plugin": ^2.3.14 + clsx: ^2.1.1 eslint-plugin-storybook: ^0.11.1 globals: ^15.11.0 mini-css-extract-plugin: ^2.9.2 From 6ddf198f29ce4595d97c5be12bc908d60f527978 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Tue, 17 Dec 2024 18:42:38 +0000 Subject: [PATCH 025/420] Fix report Signed-off-by: Charles de Dreuille --- packages/canon/report.api.md | 37 +++++++++++++++++++ packages/canon/src/components/Field/Field.tsx | 1 + packages/canon/src/components/Field/index.ts | 16 ++++++++ packages/canon/src/components/Input/Input.tsx | 1 + packages/canon/src/components/Input/index.ts | 16 ++++++++ packages/canon/src/index.ts | 2 + 6 files changed, 73 insertions(+) create mode 100644 packages/canon/src/components/Field/index.ts create mode 100644 packages/canon/src/components/Input/index.ts diff --git a/packages/canon/report.api.md b/packages/canon/report.api.md index d3f9e6fa80..e50077c7df 100644 --- a/packages/canon/report.api.md +++ b/packages/canon/report.api.md @@ -5,7 +5,9 @@ ```ts /// +import { Field as Field_2 } from '@base-ui-components/react/field'; import { ForwardRefExoticComponent } from 'react'; +import { Input as Input_2 } from '@base-ui-components/react/input'; import { default as React_2 } from 'react'; import * as React_3 from 'react'; import { RefAttributes } from 'react'; @@ -216,6 +218,35 @@ export type DisplayProps = | 'block' | Partial>; +// @public (undocumented) +export const Field: { + Root: React_2.ForwardRefExoticComponent< + Omit, 'ref'> & + React_2.RefAttributes + >; + Label: React_2.ForwardRefExoticComponent< + Omit, 'ref'> & + React_2.RefAttributes + >; + Description: React_2.ForwardRefExoticComponent< + Omit< + Field_2.Description.Props & React_2.RefAttributes, + 'ref' + > & + React_2.RefAttributes + >; + Error: React_2.ForwardRefExoticComponent< + Omit, 'ref'> & + React_2.RefAttributes + >; + Validity: ({ + children, + ...props + }: React_2.ComponentPropsWithoutRef< + typeof Field_2.Validity + >) => React_2.JSX.Element; +}; + // @public (undocumented) export type FlexDirectionProps = | 'row' @@ -331,6 +362,12 @@ export interface InlineProps extends SpaceProps, ColorProps { style?: React.CSSProperties; } +// @public (undocumented) +export const Input: React_2.ForwardRefExoticComponent< + Omit, 'ref'> & + React_2.RefAttributes +>; + // @public (undocumented) export type JustifyContentProps = | 'stretch' diff --git a/packages/canon/src/components/Field/Field.tsx b/packages/canon/src/components/Field/Field.tsx index 78cc638662..a121baab9f 100644 --- a/packages/canon/src/components/Field/Field.tsx +++ b/packages/canon/src/components/Field/Field.tsx @@ -75,6 +75,7 @@ const FieldValidity = ({ ); +/** @public */ export const Field = { Root: FieldRoot, Label: FieldLabel, diff --git a/packages/canon/src/components/Field/index.ts b/packages/canon/src/components/Field/index.ts new file mode 100644 index 0000000000..b96bd91ebe --- /dev/null +++ b/packages/canon/src/components/Field/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { Field } from './Field'; diff --git a/packages/canon/src/components/Input/Input.tsx b/packages/canon/src/components/Input/Input.tsx index c5b3aad5f5..ca90e94f8f 100644 --- a/packages/canon/src/components/Input/Input.tsx +++ b/packages/canon/src/components/Input/Input.tsx @@ -18,6 +18,7 @@ import React from 'react'; import { Input as InputPrimitive } from '@base-ui-components/react/input'; import clsx from 'clsx'; +/** @public */ const Input = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef diff --git a/packages/canon/src/components/Input/index.ts b/packages/canon/src/components/Input/index.ts new file mode 100644 index 0000000000..a757e30478 --- /dev/null +++ b/packages/canon/src/components/Input/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { Input } from './Input'; diff --git a/packages/canon/src/index.ts b/packages/canon/src/index.ts index d6a375b8dd..e06207bd06 100644 --- a/packages/canon/src/index.ts +++ b/packages/canon/src/index.ts @@ -35,3 +35,5 @@ export * from './components/Button'; export * from './components/Icon'; export * from './components/Checkbox'; export * from './components/Table'; +export * from './components/Field'; +export * from './components/Input'; From 4c5df2c59fec667439b1d6d12da7d216ceaad0c2 Mon Sep 17 00:00:00 2001 From: nikolar Date: Tue, 17 Dec 2024 17:54:01 -0800 Subject: [PATCH 026/420] update review comments Signed-off-by: nikolar --- .../home/components/Grids/InfoCardGrid.tsx | 38 +++++++++---------- .../home/components/TechDocsCustomHome.tsx | 4 +- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx b/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx index 31435debf7..cba00076b1 100644 --- a/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx +++ b/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx @@ -72,28 +72,26 @@ export const InfoCardGrid = (props: InfoCardGridProps) => { }); }; - if (!entities) return null; + if (!entities || !entities?.length) return null; return ( - {!entities?.length - ? null - : entities.map(entity => ( - -
{entity?.metadata?.description}
-
- - {linkContent || 'Read Docs'} - - - ))} + {entities.map(entity => ( + +
{entity?.metadata?.description}
+
+ + {linkContent || 'Read Docs'} + + + ))} ); }; diff --git a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx index 3c213d827c..2dcf6aae80 100644 --- a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx +++ b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx @@ -148,9 +148,9 @@ export const CustomDocsPanel = ({ return ( <> - {config.panelProps?.showHeader !== false && ( + {!!config.panelProps?.showHeader && ( - {index === 0 && config.panelProps?.showSupport !== false && ( + {index === 0 && !!config.panelProps?.showSupport && ( Discover documentation in your ecosystem. From 7c4ea6c728773a294c2a569316372386f2ed9e6c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 18 Dec 2024 14:09:18 +0000 Subject: [PATCH 027/420] chore(deps): update dependency globals to v15.14.0 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- storybook/yarn.lock | 6 +++--- yarn.lock | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/storybook/yarn.lock b/storybook/yarn.lock index be21a0fead..deedd4e770 100644 --- a/storybook/yarn.lock +++ b/storybook/yarn.lock @@ -3126,9 +3126,9 @@ __metadata: linkType: hard "globals@npm:^15.9.0": - version: 15.12.0 - resolution: "globals@npm:15.12.0" - checksum: 2a134cc876dd73192489561e3c85be348dc1408fef043ebef605cdc437f64cd2fc922268db02e3348683d05d06bed10fb1c3653b3d4399a204a7ecd59e742a07 + version: 15.14.0 + resolution: "globals@npm:15.14.0" + checksum: fa993433a01bf4a118904fbafbcff34db487fce83f73da75fb4a8653afc6dcd72905e6208c49bab307ff0980928273d0ecd1cfc67e1a4782dabfbd92c234ab68 languageName: node linkType: hard diff --git a/yarn.lock b/yarn.lock index f80bec43c0..474898061b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -30367,18 +30367,18 @@ __metadata: linkType: hard "globals@npm:^13.19.0": - version: 13.20.0 - resolution: "globals@npm:13.20.0" + version: 13.24.0 + resolution: "globals@npm:13.24.0" dependencies: type-fest: ^0.20.2 - checksum: ad1ecf914bd051325faad281d02ea2c0b1df5d01bd94d368dcc5513340eac41d14b3c61af325768e3c7f8d44576e72780ec0b6f2d366121f8eec6e03c3a3b97a + checksum: 56066ef058f6867c04ff203b8a44c15b038346a62efbc3060052a1016be9f56f4cf0b2cd45b74b22b81e521a889fc7786c73691b0549c2f3a6e825b3d394f43c languageName: node linkType: hard "globals@npm:^15.11.0": - version: 15.12.0 - resolution: "globals@npm:15.12.0" - checksum: 2a134cc876dd73192489561e3c85be348dc1408fef043ebef605cdc437f64cd2fc922268db02e3348683d05d06bed10fb1c3653b3d4399a204a7ecd59e742a07 + version: 15.14.0 + resolution: "globals@npm:15.14.0" + checksum: fa993433a01bf4a118904fbafbcff34db487fce83f73da75fb4a8653afc6dcd72905e6208c49bab307ff0980928273d0ecd1cfc67e1a4782dabfbd92c234ab68 languageName: node linkType: hard From ddf8d7cbcbacdb3b1c277f7a3a1495034b386cf3 Mon Sep 17 00:00:00 2001 From: Josh Santos Date: Wed, 18 Dec 2024 21:24:37 +0700 Subject: [PATCH 028/420] Update environment variable name in kubernetes deployment docs Signed-off-by: Josh Santos --- docs/deployment/k8s.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/deployment/k8s.md b/docs/deployment/k8s.md index 46ba273e14..a2939dcae7 100644 --- a/docs/deployment/k8s.md +++ b/docs/deployment/k8s.md @@ -387,8 +387,8 @@ $ yarn build-image --tag backstage:1.0.0 ``` There is no special wiring needed to access the PostgreSQL service. Since it's -running on the same cluster, Kubernetes will inject `POSTGRES_SERVICE_HOST` and -`POSTGRES_SERVICE_PORT` environment variables into our Backstage container. +running on the same cluster, Kubernetes will inject `POSTGRES_HOST` and +`POSTGRES_PORT` environment variables into our Backstage container. These can be used in the Backstage `app-config.yaml` along with the secrets. Apply this to `app-config.production.yaml` as well if you have one: ```yaml @@ -396,8 +396,8 @@ backend: database: client: pg connection: - host: ${POSTGRES_SERVICE_HOST} - port: ${POSTGRES_SERVICE_PORT} + host: ${POSTGRES_HOST} + port: ${POSTGRES_PORT} user: ${POSTGRES_USER} password: ${POSTGRES_PASSWORD} ``` From 45962b004f7ef4621a9f0fecc238503ee834fe77 Mon Sep 17 00:00:00 2001 From: Josh Santos Date: Wed, 18 Dec 2024 21:30:37 +0700 Subject: [PATCH 029/420] Update deployment env vars Signed-off-by: Josh Santos Run prettier for docs Signed-off-by: Josh Santos --- docs/deployment/k8s.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/deployment/k8s.md b/docs/deployment/k8s.md index a2939dcae7..1a225d6d12 100644 --- a/docs/deployment/k8s.md +++ b/docs/deployment/k8s.md @@ -210,6 +210,11 @@ spec: envFrom: - secretRef: name: postgres-secrets + env: + - name: POSTGRES_HOST + value: postgres.backstage + - name: POSTGRES_PORT + value: '5432' volumeMounts: - mountPath: /var/lib/postgresql/data name: postgresdb From ba17e05f6ccb552e9f1327fa8aeb8088af6ff5c8 Mon Sep 17 00:00:00 2001 From: nikolar Date: Wed, 18 Dec 2024 11:52:23 -0800 Subject: [PATCH 030/420] fix error with suggestion for showHeader and showSupport Signed-off-by: nikolar --- plugins/techdocs/src/home/components/TechDocsCustomHome.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx index 2dcf6aae80..b8998b8655 100644 --- a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx +++ b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx @@ -148,9 +148,9 @@ export const CustomDocsPanel = ({ return ( <> - {!!config.panelProps?.showHeader && ( + {(config.panelProps?.showHeader ?? true) && ( - {index === 0 && !!config.panelProps?.showSupport && ( + {index === 0 && (config.panelProps?.showSupport ?? true) && ( Discover documentation in your ecosystem. From d8f9079fafde47cb3822893858c016b95ee7d03f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 18 Dec 2024 21:12:08 +0000 Subject: [PATCH 031/420] fix(deps): update rjsf monorepo to v5.23.2 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .changeset/renovate-dcf1169.md | 11 +++++ plugins/home-react/package.json | 2 +- plugins/home/package.json | 8 ++-- plugins/scaffolder-react/package.json | 8 ++-- plugins/scaffolder/package.json | 8 ++-- yarn.lock | 58 +++++++++++++-------------- 6 files changed, 53 insertions(+), 42 deletions(-) create mode 100644 .changeset/renovate-dcf1169.md diff --git a/.changeset/renovate-dcf1169.md b/.changeset/renovate-dcf1169.md new file mode 100644 index 0000000000..b23fd46c77 --- /dev/null +++ b/.changeset/renovate-dcf1169.md @@ -0,0 +1,11 @@ +--- +'@backstage/plugin-home-react': patch +'@backstage/plugin-home': patch +'@backstage/plugin-scaffolder-react': patch +'@backstage/plugin-scaffolder': patch +--- + +Updated dependency `@rjsf/utils` to `5.23.2`. +Updated dependency `@rjsf/core` to `5.23.2`. +Updated dependency `@rjsf/material-ui` to `5.23.2`. +Updated dependency `@rjsf/validator-ajv8` to `5.23.2`. diff --git a/plugins/home-react/package.json b/plugins/home-react/package.json index 5d17c8626b..2630c05a2a 100644 --- a/plugins/home-react/package.json +++ b/plugins/home-react/package.json @@ -46,7 +46,7 @@ "@backstage/core-plugin-api": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", - "@rjsf/utils": "5.23.1" + "@rjsf/utils": "5.23.2" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/plugins/home/package.json b/plugins/home/package.json index efadf8aaeb..59331214b6 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -70,10 +70,10 @@ "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", - "@rjsf/core": "5.23.1", - "@rjsf/material-ui": "5.23.1", - "@rjsf/utils": "5.23.1", - "@rjsf/validator-ajv8": "5.23.1", + "@rjsf/core": "5.23.2", + "@rjsf/material-ui": "5.23.2", + "@rjsf/utils": "5.23.2", + "@rjsf/validator-ajv8": "5.23.2", "lodash": "^4.17.21", "luxon": "^3.4.3", "react-grid-layout": "1.3.4", diff --git a/plugins/scaffolder-react/package.json b/plugins/scaffolder-react/package.json index 6ea25a5e65..ec9477d3e8 100644 --- a/plugins/scaffolder-react/package.json +++ b/plugins/scaffolder-react/package.json @@ -73,10 +73,10 @@ "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", "@react-hookz/web": "^24.0.0", - "@rjsf/core": "5.23.1", - "@rjsf/material-ui": "5.23.1", - "@rjsf/utils": "5.23.1", - "@rjsf/validator-ajv8": "5.23.1", + "@rjsf/core": "5.23.2", + "@rjsf/material-ui": "5.23.2", + "@rjsf/utils": "5.23.2", + "@rjsf/validator-ajv8": "5.23.2", "@types/json-schema": "^7.0.9", "ajv-errors": "^3.0.0", "classnames": "^2.2.6", diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 549c47da7b..89f1545efc 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -81,10 +81,10 @@ "@material-ui/lab": "4.0.0-alpha.61", "@microsoft/fetch-event-source": "^2.0.1", "@react-hookz/web": "^24.0.0", - "@rjsf/core": "5.23.1", - "@rjsf/material-ui": "5.23.1", - "@rjsf/utils": "5.23.1", - "@rjsf/validator-ajv8": "5.23.1", + "@rjsf/core": "5.23.2", + "@rjsf/material-ui": "5.23.2", + "@rjsf/utils": "5.23.2", + "@rjsf/validator-ajv8": "5.23.2", "@uiw/react-codemirror": "^4.9.3", "classnames": "^2.2.6", "git-url-parse": "^15.0.0", diff --git a/yarn.lock b/yarn.lock index 58d08823e1..c0dfad935e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6395,7 +6395,7 @@ __metadata: "@backstage/core-plugin-api": "workspace:^" "@material-ui/core": ^4.12.2 "@material-ui/icons": ^4.9.1 - "@rjsf/utils": 5.23.1 + "@rjsf/utils": 5.23.2 "@types/react": ^18.0.0 "@types/react-grid-layout": ^1.3.2 react: ^18.0.2 @@ -6433,10 +6433,10 @@ __metadata: "@material-ui/core": ^4.12.2 "@material-ui/icons": ^4.9.1 "@material-ui/lab": 4.0.0-alpha.61 - "@rjsf/core": 5.23.1 - "@rjsf/material-ui": 5.23.1 - "@rjsf/utils": 5.23.1 - "@rjsf/validator-ajv8": 5.23.1 + "@rjsf/core": 5.23.2 + "@rjsf/material-ui": 5.23.2 + "@rjsf/utils": 5.23.2 + "@rjsf/validator-ajv8": 5.23.2 "@testing-library/dom": ^10.0.0 "@testing-library/jest-dom": ^6.0.0 "@testing-library/react": ^16.0.0 @@ -7483,10 +7483,10 @@ __metadata: "@material-ui/icons": ^4.9.1 "@material-ui/lab": 4.0.0-alpha.61 "@react-hookz/web": ^24.0.0 - "@rjsf/core": 5.23.1 - "@rjsf/material-ui": 5.23.1 - "@rjsf/utils": 5.23.1 - "@rjsf/validator-ajv8": 5.23.1 + "@rjsf/core": 5.23.2 + "@rjsf/material-ui": 5.23.2 + "@rjsf/utils": 5.23.2 + "@rjsf/validator-ajv8": 5.23.2 "@testing-library/dom": ^10.0.0 "@testing-library/jest-dom": ^6.0.0 "@testing-library/react": ^16.0.0 @@ -7557,10 +7557,10 @@ __metadata: "@material-ui/lab": 4.0.0-alpha.61 "@microsoft/fetch-event-source": ^2.0.1 "@react-hookz/web": ^24.0.0 - "@rjsf/core": 5.23.1 - "@rjsf/material-ui": 5.23.1 - "@rjsf/utils": 5.23.1 - "@rjsf/validator-ajv8": 5.23.1 + "@rjsf/core": 5.23.2 + "@rjsf/material-ui": 5.23.2 + "@rjsf/utils": 5.23.2 + "@rjsf/validator-ajv8": 5.23.2 "@testing-library/dom": ^10.0.0 "@testing-library/jest-dom": ^6.0.0 "@testing-library/react": ^16.0.0 @@ -15348,9 +15348,9 @@ __metadata: languageName: node linkType: hard -"@rjsf/core@npm:5.23.1": - version: 5.23.1 - resolution: "@rjsf/core@npm:5.23.1" +"@rjsf/core@npm:5.23.2": + version: 5.23.2 + resolution: "@rjsf/core@npm:5.23.2" dependencies: lodash: ^4.17.21 lodash-es: ^4.17.21 @@ -15360,26 +15360,26 @@ __metadata: peerDependencies: "@rjsf/utils": ^5.23.x react: ^16.14.0 || >=17 - checksum: acb5b1541b7e6f9911dce33455c297402fc1b2278b0c688073decdea977efae7d4227962eaadeb48fd14c2a8e4bba73a80df975b1c49aa2e2b933c2646ab4904 + checksum: 36b2505afd5402368a31a06a4b9d2264f63cab9766f2060cd3c3ecf8b4c08fc7fc8b1b82dd00788f357a2ca649d76c5b6e324152572dbf333bd2b93a0bcc99fd languageName: node linkType: hard -"@rjsf/material-ui@npm:5.23.1": - version: 5.23.1 - resolution: "@rjsf/material-ui@npm:5.23.1" +"@rjsf/material-ui@npm:5.23.2": + version: 5.23.2 + resolution: "@rjsf/material-ui@npm:5.23.2" peerDependencies: "@material-ui/core": ^4.12.3 "@material-ui/icons": ^4.11.2 "@rjsf/core": ^5.23.x "@rjsf/utils": ^5.23.x react: ^16.14.0 || >=17 - checksum: ae0d401edd407c534406cce60fda2725fc246286cbedd8a8e4031097d4d318761fd8a4f35339d0f3f857a3dc863b88a75002d6900176052913bb629d0ebde4f9 + checksum: 3c41a4d3133bfb1ddf2a9f96fdf6b44de7d16688133a5a69833b8a99044b499f1466d361e6e92e04b88d281ca9b1819905bafd01fee6f4e0329d111de0eb5c0a languageName: node linkType: hard -"@rjsf/utils@npm:5.23.1": - version: 5.23.1 - resolution: "@rjsf/utils@npm:5.23.1" +"@rjsf/utils@npm:5.23.2": + version: 5.23.2 + resolution: "@rjsf/utils@npm:5.23.2" dependencies: json-schema-merge-allof: ^0.8.1 jsonpointer: ^5.0.1 @@ -15388,13 +15388,13 @@ __metadata: react-is: ^18.2.0 peerDependencies: react: ^16.14.0 || >=17 - checksum: 7580419cf07416fe1e608ed171c30b25b3a78cfebba7d97e3120fe2e40f702fd0e61494e6c823281091522b053a39a83ab31ceb97c078cfb39ac636dc2d997c1 + checksum: 16980013258bab7accaff961c533e4bb8e3326c37a84670a7667b2a10c1ca395451eb51a6cf819ccbafb1aa8838df325ff1f314b410bb186fef98856135e1a06 languageName: node linkType: hard -"@rjsf/validator-ajv8@npm:5.23.1": - version: 5.23.1 - resolution: "@rjsf/validator-ajv8@npm:5.23.1" +"@rjsf/validator-ajv8@npm:5.23.2": + version: 5.23.2 + resolution: "@rjsf/validator-ajv8@npm:5.23.2" dependencies: ajv: ^8.12.0 ajv-formats: ^2.1.1 @@ -15402,7 +15402,7 @@ __metadata: lodash-es: ^4.17.21 peerDependencies: "@rjsf/utils": ^5.23.x - checksum: 3eca428bd682ea8226558e0c719f263912ad8d6fde2c3ee817c5c106070fd3142f614dc35e15819000f8f00f9c00aa654c2dab5fd3a7318164ad6420d53068f5 + checksum: da6328ac6ddc448141dd183fa6447a0ff5b0bcc7c77c8fa4d9d0a35b59727095da72fb15b16ded6c1d3769ca19cadb781da6ebc23c7cb79f87c83bca5d58a0fb languageName: node linkType: hard From ddefc815c60f8930494efef05d96aa4aa4df3336 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 18 Dec 2024 22:37:15 +0000 Subject: [PATCH 032/420] chore(deps): update actions/upload-artifact action to v4.5.0 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/pr-review-comment-trigger.yaml | 2 +- .github/workflows/scorecard.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-review-comment-trigger.yaml b/.github/workflows/pr-review-comment-trigger.yaml index b5027d78be..344425ca52 100644 --- a/.github/workflows/pr-review-comment-trigger.yaml +++ b/.github/workflows/pr-review-comment-trigger.yaml @@ -30,7 +30,7 @@ jobs: run: | mkdir -p ./pr echo $PR_NUMBER > ./pr/pr_number - - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + - uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: pr_number-${{ github.event.pull_request.number }} path: pr/ diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index db060a70d4..fc5323af63 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -58,7 +58,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: 'Upload artifact' - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: SARIF file path: results.sarif From 1cce2d59f62a064fed6edef87a1cbcf5fc88c761 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 19 Dec 2024 00:47:23 +0000 Subject: [PATCH 033/420] chore(deps): update docker/setup-buildx-action action to v3.8.0 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/deploy_docker-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy_docker-image.yml b/.github/workflows/deploy_docker-image.yml index a1764b976a..2285f61f48 100644 --- a/.github/workflows/deploy_docker-image.yml +++ b/.github/workflows/deploy_docker-image.yml @@ -59,7 +59,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 - name: Build and push uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0 From 4ab00e4bb7496143d26aca7b06471703d86413e0 Mon Sep 17 00:00:00 2001 From: Teijo Mursu Date: Thu, 19 Dec 2024 12:21:07 +0200 Subject: [PATCH 034/420] fix(catalog-backend-module-github): update parent to not send a object with empty string Signed-off-by: Teijo Mursu --- .changeset/weak-frogs-nail.md | 5 + .../GithubMultiOrgEntityProvider.test.ts | 220 ++++++++++++++++++ .../providers/GithubMultiOrgEntityProvider.ts | 8 +- 3 files changed, 231 insertions(+), 2 deletions(-) create mode 100644 .changeset/weak-frogs-nail.md diff --git a/.changeset/weak-frogs-nail.md b/.changeset/weak-frogs-nail.md new file mode 100644 index 0000000000..cf6c5d3048 --- /dev/null +++ b/.changeset/weak-frogs-nail.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-github': patch +--- + +Fixes an issue in `GithubMultiOrgEntityProvider` that caused an error when processing teams without a parent. diff --git a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.test.ts index df49ac9803..2186b11f50 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.test.ts @@ -1596,6 +1596,62 @@ describe('GithubMultiOrgEntityProvider', () => { }); }); + it('should create a new group from a new team without parent', async () => { + await events.publish({ + topic: 'github.team', + eventPayload: { + action: 'created', + organization: { + login: 'orgB', + }, + team: { + name: 'New Team', + slug: 'new-team', + description: 'description from the new team', + html_url: 'https://github.com/orgs/orgB/teams/new-team', + parent: null, + }, + }, + }); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1); + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ + type: 'delta', + added: [ + { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { + name: 'new-team', + namespace: 'orgb', + description: 'description from the new team', + annotations: { + 'backstage.io/edit-url': + 'https://github.com/orgs/orgB/teams/new-team/edit', + 'backstage.io/managed-by-location': + 'url:https://github.com/orgs/orgB/teams/new-team', + 'backstage.io/managed-by-origin-location': + 'url:https://github.com/orgs/orgB/teams/new-team', + 'github.com/team-slug': 'orgB/new-team', + }, + }, + spec: { + type: 'team', + children: [], + members: [], + profile: { + displayName: 'New Team', + }, + }, + }, + locationKey: 'github-multi-org-provider:my-id', + }, + ], + removed: [], + }); + }); + it('should remove a group from a deleted team', async () => { await events.publish({ topic: 'github.team', @@ -1869,6 +1925,170 @@ describe('GithubMultiOrgEntityProvider', () => { ], }); }); + + it('should update group without parent', async () => { + const mockClient = jest.fn(); + + mockClient + .mockResolvedValueOnce({ + organization: { + team: { + slug: 'team', + combinedSlug: 'orgA/team', + name: 'TeamA', + description: 'The one and only team', + avatarUrl: 'http://example.com/team.jpeg', + editTeamUrl: 'https://example.com', + parentTeam: null, + members: { + pageInfo: { hasNextPage: false }, + nodes: [], + }, + }, + }, + }) + .mockResolvedValueOnce({ + organization: { + membersWithRole: { + pageInfo: { hasNextPage: false }, + nodes: [], + }, + }, + }) + .mockResolvedValueOnce({ + organization: { + teams: { + pageInfo: { hasNextPage: false }, + nodes: [ + { + slug: 'team', + combinedSlug: 'orgA/team', + name: 'TeamA', + description: 'The one and only team', + avatarUrl: 'http://example.com/team.jpeg', + editTeamUrl: 'https://example.com', + parentTeam: null, + members: { + pageInfo: { hasNextPage: false }, + nodes: [], + }, + }, + ], + }, + }, + }) + .mockResolvedValueOnce({ + organization: { + teams: { + pageInfo: { hasNextPage: false }, + nodes: [ + { + slug: 'team', + combinedSlug: 'orgB/team', + name: 'TeamB', + description: 'The one and only team', + avatarUrl: 'http://example.com/team.jpeg', + editTeamUrl: 'https://example.com', + parentTeam: null, + members: { + pageInfo: { hasNextPage: false }, + nodes: [], + }, + }, + ], + }, + }, + }); + + (graphql.defaults as jest.Mock).mockReturnValue(mockClient); + + await events.publish({ + topic: 'github.team', + eventPayload: { + action: 'edited', + changes: { + name: { + from: 'oldName', + }, + description: { + from: 'oldDescription', + }, + }, + team: { + slug: 'team', + parent: null, + }, + organization: { + login: 'orgA', + }, + }, + }); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1); + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ + type: 'delta', + added: [ + { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { + annotations: { + 'backstage.io/edit-url': 'https://example.com', + 'backstage.io/managed-by-location': + 'url:https://github.com/orgs/orgA/teams/team', + 'backstage.io/managed-by-origin-location': + 'url:https://github.com/orgs/orgA/teams/team', + 'github.com/team-slug': 'orgA/team', + }, + namespace: 'orga', + name: 'team', + description: 'The one and only team', + }, + spec: { + children: [], + profile: { + displayName: 'TeamA', + picture: 'http://example.com/team.jpeg', + }, + type: 'team', + members: [], + }, + }, + locationKey: 'github-multi-org-provider:my-id', + }, + ], + removed: [ + { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { + annotations: { + 'backstage.io/managed-by-location': + 'url:https://github.com/orgs/orgA/teams/oldname', + 'backstage.io/managed-by-origin-location': + 'url:https://github.com/orgs/orgA/teams/oldname', + 'github.com/team-slug': 'orgA/oldname', + }, + namespace: 'orga', + name: 'oldname', + description: 'oldDescription', + }, + spec: { + children: [], + profile: { + displayName: 'oldName', + }, + type: 'team', + members: [], + }, + }, + locationKey: 'github-multi-org-provider:my-id', + }, + ], + }); + }); }); describe('membership', () => { diff --git a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts index 4367383cd2..fbfdc8a20d 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts @@ -602,7 +602,9 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { editTeamUrl: `${url}/edit`, combinedSlug: `${org}/${slug}`, description: description ?? undefined, - parentTeam: { slug: event.team?.parent?.slug || '' } as GithubTeam, + parentTeam: event.team?.parent?.slug + ? ({ slug: event.team.parent.slug } as GithubTeam) + : undefined, // entity will be removed or is new members: [], }, @@ -705,7 +707,9 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { slug: oldSlug, combinedSlug: `${org}/${oldSlug}`, description: event.changes.description?.from, - parentTeam: { slug: event.team?.parent?.slug || '' } as GithubTeam, + parentTeam: event.team?.parent?.slug + ? ({ slug: event.team.parent.slug } as GithubTeam) + : undefined, // entity will be removed members: [], }, From 7d635e1e86b26222bed10bd8b9f579d0315358ec Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Thu, 19 Dec 2024 13:16:10 -0600 Subject: [PATCH 035/420] Removed new Signed-off-by: Andre Wanlin --- docs/features/software-templates/writing-custom-actions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/software-templates/writing-custom-actions.md b/docs/features/software-templates/writing-custom-actions.md index 11e67af07f..8cfc5464f2 100644 --- a/docs/features/software-templates/writing-custom-actions.md +++ b/docs/features/software-templates/writing-custom-actions.md @@ -220,7 +220,7 @@ const scaffolderModuleCustomExtensions = createBackendModule({ async init({ scaffolder /* ..., other dependencies */ }) { // Here you have the opportunity to interact with the extension // point before the plugin itself gets instantiated - scaffolder.addActions(new createNewFileAction()); // just an example + scaffolder.addActions(createNewFileAction()); // just an example }, }); }, From 0fc1936db42bd724f1c93e6df9aa3edc4798913e Mon Sep 17 00:00:00 2001 From: nikolar Date: Fri, 20 Dec 2024 17:39:44 -0800 Subject: [PATCH 036/420] fix custom props Signed-off-by: nikolar --- .changeset/warm-masks-ring.md | 17 ++-- docs/features/techdocs/how-to-guides.md | 10 +-- .../home/components/DefaultTechDocsHome.tsx | 31 +++---- .../components/TechDocsCustomHome.test.tsx | 81 +++---------------- .../home/components/TechDocsCustomHome.tsx | 50 +++++------- .../src/home/components/TechDocsIndexPage.tsx | 8 +- .../home/components/TechDocsPageWrapper.tsx | 34 ++++---- plugins/techdocs/src/index.ts | 2 + 8 files changed, 83 insertions(+), 150 deletions(-) diff --git a/.changeset/warm-masks-ring.md b/.changeset/warm-masks-ring.md index aefbcf8013..bfb521a005 100644 --- a/.changeset/warm-masks-ring.md +++ b/.changeset/warm-masks-ring.md @@ -20,7 +20,7 @@ const techDocsTabsConfig = [ title: 'Golden Path', description: 'Documentation about standards to follow', panelType: 'DocsCardGrid', - panelProps: { showHeader: false, showSupport: false }, + panelProps: { CustomHeader: () => }, filterPredicate: entity => entity?.metadata?.tags?.includes('golden-path') ?? false, }, @@ -29,8 +29,7 @@ const techDocsTabsConfig = [ description: 'Useful documentation', panelType: 'InfoCardGrid', panelProps: { - showHeader: false, - showSupport: false, + CustomHeader: () => linkDestination: linkDestination, }, filterPredicate: entity => @@ -46,7 +45,7 @@ const techDocsTabsConfig = [ filterPredicate: filterEntity, panelType: 'TechDocsIndexPage', title: 'All', - panelProps: { showHeader: false, showSupport: false, options: options }, + panelProps: { PageWrapper: React.Fragment, CustomHeader: React.Fragment, options: options }, }, ], }, @@ -59,12 +58,11 @@ const AppRoutes = () => { element={ ) => ({children})} /> } /> @@ -92,10 +90,9 @@ const panels: PanelConfig[] = [ panelType: 'InfoCardGrid', title: 'Standards', panelProps: { - showSupport: false, - linkContent: 'Read more', - linkDestination: entity => {}, - }, + CustomHeader: () => + linkDestination: linkDestination, + }, }, { description: '', diff --git a/docs/features/techdocs/how-to-guides.md b/docs/features/techdocs/how-to-guides.md index ea1ac7a372..5a9b8ba140 100644 --- a/docs/features/techdocs/how-to-guides.md +++ b/docs/features/techdocs/how-to-guides.md @@ -149,7 +149,7 @@ const techDocsTabsConfig = [ title: 'Golden Path', description: 'Documentation about standards to follow', panelType: 'DocsCardGrid', - panelProps: { showHeader: false, showSupport: false }, + panelProps: { CustomHeader: () => }, filterPredicate: entity => entity?.metadata?.tags?.includes('golden-path') ?? false, }, @@ -158,8 +158,7 @@ const techDocsTabsConfig = [ description: 'Useful documentation', panelType: 'InfoCardGrid', panelProps: { - showHeader: false, - showSupport: false, + CustomHeader: () => linkDestination: linkDestination, }, filterPredicate: entity => @@ -175,7 +174,7 @@ const techDocsTabsConfig = [ filterPredicate: filterEntity, panelType: 'TechDocsIndexPage', title: 'All', - panelProps: { showHeader: false, showSupport: false, options: options }, + panelProps: { PageWrapper: React.Fragment, CustomHeader: React.Fragment, options: options }, }, ], }, @@ -188,12 +187,11 @@ const AppRoutes = () => { element={ ) => ({children})} /> } /> diff --git a/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx b/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx index 2f79455ad3..f44bc8eca0 100644 --- a/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx +++ b/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx @@ -51,24 +51,27 @@ export const DefaultTechDocsHome = (props: TechDocsIndexPageProps) => { columns, actions, ownerPickerMode, - showHeader = true, - options, - title, - subtitle, - showSupport = true, pagination, + options, + PageWrapper, + CustomHeader, } = props; - const Wrapper = showHeader ? TechDocsPageWrapper : React.Fragment; + const Wrapper: React.FC<{ + children: React.ReactNode; + title?: string; + subtitle?: string; + }> = PageWrapper ? PageWrapper : TechDocsPageWrapper; + const Header: React.FC = + CustomHeader || + (() => ( + + Discover documentation in your ecosystem. + + )); return ( - + - {showSupport && ( - - - Discover documentation in your ecosystem. - - - )} +
diff --git a/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx b/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx index a366cc7269..bd9725d9a0 100644 --- a/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx +++ b/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx @@ -19,6 +19,7 @@ import { starredEntitiesApiRef, MockStarredEntitiesApi, } from '@backstage/plugin-catalog-react'; +import { PageWithHeader } from '@backstage/core-components'; import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils'; import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils'; import { screen } from '@testing-library/react'; @@ -103,7 +104,7 @@ describe('TechDocsCustomHome', () => { await screen.findByText('Second Tab Description'), ).toBeInTheDocument(); }); - it('should render ContentHeader based on showHeader prop', async () => { + it('should render ContentHeader based on CustomHeader prop', async () => { const tabsConfig = [ { label: 'First Tab', @@ -112,7 +113,7 @@ describe('TechDocsCustomHome', () => { title: 'First Tab', description: 'First Tab Description', panelType: 'DocsCardGrid' as PanelType, - panelProps: { showHeader: false }, + panelProps: { CustomHeader: React.Fragment }, filterPredicate: () => true, }, ], @@ -134,71 +135,6 @@ describe('TechDocsCustomHome', () => { screen.queryByText('Discover documentation in your ecosystem.'), ).not.toBeInTheDocument(); }); - it('should render SupportButton based on showSupport prop', async () => { - const tabsConfig = [ - { - label: 'First Tab', - panels: [ - { - title: 'First Tab', - description: 'First Tab Description', - panelType: 'DocsCardGrid' as PanelType, - filterPredicate: () => true, - panelProps: { showSupport: false }, - }, - ], - }, - ]; - - await renderInTestApp( - - - , - { - mountedRoutes: { - '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, - }, - }, - ); - - expect( - screen.queryByText('Discover documentation in your ecosystem.'), - ).not.toBeInTheDocument(); - }); - it('should hide subtitle when showSubtitle is false', async () => { - const tabsConfig = [ - { - label: 'First Tab', - panels: [ - { - title: 'First Tab', - description: 'First Tab Description', - panelType: 'DocsCardGrid' as PanelType, - filterPredicate: () => true, - }, - ], - }, - ]; - - await renderInTestApp( - - - , - { - mountedRoutes: { - '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, - }, - }, - ); - - expect(screen.getByText('Custom Title')).toBeInTheDocument(); - expect(screen.queryByText('Custom Subtitle')).not.toBeInTheDocument(); - }); it('should render title and subtitle', async () => { const tabsConfig = [ { @@ -218,8 +154,15 @@ describe('TechDocsCustomHome', () => { ) => ( + + {children} + + )} /> , { diff --git a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx index b8998b8655..e1c5a93161 100644 --- a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx +++ b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx @@ -69,11 +69,11 @@ export type PanelType = * @public */ export interface PanelProps { - showHeader?: boolean; - showSupport?: boolean; options?: TableOptions; linkContent?: string | JSX.Element; linkDestination?: (entity: Entity) => string | undefined; + PageWrapper?: React.FC; + CustomHeader?: React.FC; } /** @@ -146,17 +146,21 @@ export const CustomDocsPanel = ({ ); }); + const Header: React.FC = + config.panelProps?.CustomHeader || + (() => ( + + {index === 0 ? ( + + Discover documentation in your ecosystem. + + ) : null} + + )); + return ( <> - {(config.panelProps?.showHeader ?? true) && ( - - {index === 0 && (config.panelProps?.showSupport ?? true) && ( - - Discover documentation in your ecosystem. - - )} - - )} +
{ - const { tabsConfig, filter, title, subtitle, showSubtitle = true } = props; + const { tabsConfig, filter, CustomPageWrapper } = props; const [selectedTab, setSelectedTab] = useState(0); const catalogApi: CatalogApi = useApi(catalogApiRef); @@ -216,11 +218,7 @@ export const TechDocsCustomHome = (props: TechDocsCustomHomeProps) => { if (loading) { return ( - + @@ -230,11 +228,7 @@ export const TechDocsCustomHome = (props: TechDocsCustomHomeProps) => { if (error) { return ( - + { } return ( - + setSelectedTab(index)} diff --git a/plugins/techdocs/src/home/components/TechDocsIndexPage.tsx b/plugins/techdocs/src/home/components/TechDocsIndexPage.tsx index 50388519ba..221839c492 100644 --- a/plugins/techdocs/src/home/components/TechDocsIndexPage.tsx +++ b/plugins/techdocs/src/home/components/TechDocsIndexPage.tsx @@ -39,12 +39,10 @@ export type TechDocsIndexPageProps = { columns?: TableColumn[]; actions?: TableProps['actions']; ownerPickerMode?: EntityOwnerPickerProps['mode']; - showHeader?: boolean; - showSupport?: boolean; - options?: TableOptions; - title?: string; - subtitle?: string; pagination?: EntityListPagination; + options?: TableOptions; + PageWrapper?: React.FC; + CustomHeader?: React.FC; }; export const TechDocsIndexPage = (props: TechDocsIndexPageProps) => { diff --git a/plugins/techdocs/src/home/components/TechDocsPageWrapper.tsx b/plugins/techdocs/src/home/components/TechDocsPageWrapper.tsx index 84f8eee327..c07876f7f9 100644 --- a/plugins/techdocs/src/home/components/TechDocsPageWrapper.tsx +++ b/plugins/techdocs/src/home/components/TechDocsPageWrapper.tsx @@ -26,9 +26,7 @@ import { useApi, configApiRef } from '@backstage/core-plugin-api'; */ export type TechDocsPageWrapperProps = { children?: React.ReactNode; - title?: string; - subtitle?: string; - showSubtitle?: boolean; + CustomPageWrapper?: React.FC<{ children?: React.ReactNode }>; }; /** @@ -37,21 +35,25 @@ export type TechDocsPageWrapperProps = { * @public */ export const TechDocsPageWrapper = (props: TechDocsPageWrapperProps) => { - const { children, title, subtitle, showSubtitle = true } = props; + const { children, CustomPageWrapper } = props; const configApi = useApi(configApiRef); - const generatedSubtitle = - subtitle || - `Documentation available in ${ - configApi.getOptionalString('organization.name') ?? 'Backstage' - }`; + const generatedSubtitle = `Documentation available in ${ + configApi.getOptionalString('organization.name') ?? 'Backstage' + }`; return ( - - {children} - + <> + {CustomPageWrapper ? ( + {children} + ) : ( + + {children} + + )} + ); }; diff --git a/plugins/techdocs/src/index.ts b/plugins/techdocs/src/index.ts index 522bcd0c99..713efc17c6 100644 --- a/plugins/techdocs/src/index.ts +++ b/plugins/techdocs/src/index.ts @@ -67,3 +67,5 @@ export type { DeprecatedTechDocsEntityMetadata as TechDocsEntityMetadata, DeprecatedTechDocsMetadata as TechDocsMetadata, }; + +export * from './overridableComponents'; From d58d34ee0a39038eb72e09a7c2eb71aa42d6656a Mon Sep 17 00:00:00 2001 From: nikolar Date: Fri, 20 Dec 2024 17:46:02 -0800 Subject: [PATCH 037/420] fix api report Signed-off-by: nikolar --- plugins/techdocs/report.api.md | 38 +++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/plugins/techdocs/report.api.md b/plugins/techdocs/report.api.md index 073d843872..9fb7c151b7 100644 --- a/plugins/techdocs/report.api.md +++ b/plugins/techdocs/report.api.md @@ -18,12 +18,14 @@ import { EntityOwnerPickerProps } from '@backstage/plugin-catalog-react'; import { FetchApi } from '@backstage/core-plugin-api'; import { IdentityApi } from '@backstage/core-plugin-api'; import { JSX as JSX_2 } from 'react'; +import { Overrides } from '@material-ui/core/styles/overrides'; import { PropsWithChildren } from 'react'; import { default as React_2 } from 'react'; import { ReactNode } from 'react'; import { ResultHighlight } from '@backstage/plugin-search-common'; import { RouteRef } from '@backstage/core-plugin-api'; import { SearchResultListItemExtensionProps } from '@backstage/plugin-search-react'; +import { StyleRules } from '@material-ui/core/styles/withStyles'; import { SyncResult as SyncResult_2 } from '@backstage/plugin-techdocs-react'; import { TableColumn } from '@backstage/core-components'; import { TableOptions } from '@backstage/core-components'; @@ -36,6 +38,18 @@ import { ThemeOptions } from '@material-ui/core/styles'; import { ToolbarProps } from '@material-ui/core/Toolbar'; import { UserListFilterKind } from '@backstage/plugin-catalog-react'; +// @public (undocumented) +export type BackstageOverrides = Overrides & { + [Name in keyof CatalogReactComponentsNameToClassKey]?: Partial< + StyleRules + >; +}; + +// @public (undocumented) +export type CatalogReactComponentsNameToClassKey = { + BackstageInfoCardGrid: InfoCardGridClassKey; +}; + // @public export type ContentStateTypes = /** There is nothing to display but a loading indicator */ @@ -243,6 +257,8 @@ export interface PanelConfig { // @public export interface PanelProps { + // (undocumented) + CustomHeader?: React_2.FC; // (undocumented) linkContent?: string | JSX.Element; // (undocumented) @@ -250,9 +266,7 @@ export interface PanelProps { // (undocumented) options?: TableOptions; // (undocumented) - showHeader?: boolean; - // (undocumented) - showSupport?: boolean; + PageWrapper?: React_2.FC; } // @public @@ -342,9 +356,7 @@ export const TechDocsCustomHome: ( export type TechDocsCustomHomeProps = { tabsConfig: TabsConfig; filter?: EntityFilterQuery; - title?: string; - subtitle?: string; - showSubtitle?: boolean; + CustomPageWrapper?: React_2.FC; }; // @public @deprecated (undocumented) @@ -361,12 +373,10 @@ export type TechDocsIndexPageProps = { columns?: TableColumn[]; actions?: TableProps['actions']; ownerPickerMode?: EntityOwnerPickerProps['mode']; - showHeader?: boolean; - showSupport?: boolean; - options?: TableOptions; - title?: string; - subtitle?: string; pagination?: EntityListPagination; + options?: TableOptions; + PageWrapper?: React_2.FC; + CustomHeader?: React_2.FC; }; // @public @deprecated (undocumented) @@ -383,9 +393,9 @@ export const TechDocsPageWrapper: ( // @public export type TechDocsPageWrapperProps = { children?: React_2.ReactNode; - title?: string; - subtitle?: string; - showSubtitle?: boolean; + CustomPageWrapper?: React_2.FC<{ + children?: React_2.ReactNode; + }>; }; // @public From f1a09dd40ef8486278aabcbe1918d9b5d7a910ae Mon Sep 17 00:00:00 2001 From: nikolar Date: Fri, 20 Dec 2024 22:29:16 -0800 Subject: [PATCH 038/420] :broom: Signed-off-by: nikolar --- plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx | 2 -- .../techdocs/src/home/components/TechDocsCustomHome.test.tsx | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx b/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx index f44bc8eca0..9b6ee490a4 100644 --- a/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx +++ b/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx @@ -58,8 +58,6 @@ export const DefaultTechDocsHome = (props: TechDocsIndexPageProps) => { } = props; const Wrapper: React.FC<{ children: React.ReactNode; - title?: string; - subtitle?: string; }> = PageWrapper ? PageWrapper : TechDocsPageWrapper; const Header: React.FC = CustomHeader || diff --git a/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx b/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx index bd9725d9a0..bf3795d137 100644 --- a/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx +++ b/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx @@ -135,7 +135,7 @@ describe('TechDocsCustomHome', () => { screen.queryByText('Discover documentation in your ecosystem.'), ).not.toBeInTheDocument(); }); - it('should render title and subtitle', async () => { + it('should render CustomPageWrapper', async () => { const tabsConfig = [ { label: 'First Tab', From 22ce5183255a95dab420fde5c69bbaf4d676f2c2 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 22 Dec 2024 11:07:29 +0100 Subject: [PATCH 039/420] cli: added initial module hooks Signed-off-by: Patrik Oldsberg --- packages/cli/config/nodeTransform.cjs | 11 +- packages/cli/config/nodeTransformHooks.mjs | 281 +++++++++++++++++++++ 2 files changed, 291 insertions(+), 1 deletion(-) create mode 100644 packages/cli/config/nodeTransformHooks.mjs diff --git a/packages/cli/config/nodeTransform.cjs b/packages/cli/config/nodeTransform.cjs index 1cf79cb1bb..f54527b4a7 100644 --- a/packages/cli/config/nodeTransform.cjs +++ b/packages/cli/config/nodeTransform.cjs @@ -14,6 +14,7 @@ * limitations under the License. */ +const { pathToFileURL } = require('url'); const { transformSync } = require('@swc/core'); const { addHook } = require('pirates'); const { Module } = require('module'); @@ -55,7 +56,10 @@ addHook( const transformed = transformSync(code, { filename, sourceMaps: 'inline', - module: { type: 'commonjs' }, + module: { + type: 'commonjs', + ignoreDynamic: true, + }, jsc: { target: 'es2022', parser: { @@ -76,3 +80,8 @@ addHook( }, { extensions: ['.js', '.cjs'], ignoreNodeModules: true }, ); + +// Register module hooks, used by "type": "module" in package.json, .mjs and +// .mts files, as well as dynamic import(...)s, although dynamic imports will be +// handled be the CommonJS hooks in this file if what it points to is CommonJS. +Module.register('./nodeTransformHooks.mjs', pathToFileURL(__filename)); diff --git a/packages/cli/config/nodeTransformHooks.mjs b/packages/cli/config/nodeTransformHooks.mjs new file mode 100644 index 0000000000..5892b18ee4 --- /dev/null +++ b/packages/cli/config/nodeTransformHooks.mjs @@ -0,0 +1,281 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { dirname, extname, resolve as resolvePath } from 'path'; +import { fileURLToPath } from 'url'; +import { transformFile } from '@swc/core'; +import { isBuiltin } from 'node:module'; +import { readFile } from 'fs/promises'; +import { existsSync } from 'fs'; + +// @ts-check + +// No explicit file extension, no type in package.json +const DEFAULT_MODULE_FORMAT = 'commonjs'; + +// Source file extensions to look for when using bundle resolution strategy +const EXTS = ['.ts', '.js', '.mts', '.cts', '.mjs', '.cjs']; +const TS_EXTS = ['.ts', '.mts', '.cts']; +const moduleTypeTable = { + '.mjs': 'module', + '.mts': 'module', + '.cjs': 'commonjs', + '.cts': 'commonjs', + '.ts': undefined, + '.js': undefined, +}; + +/** @type {import('module').ResolveHook} */ +export async function resolve(specifier, context, nextResolve) { + // Built-in modules are handled by the default resolver + if (isBuiltin(specifier)) { + return nextResolve(specifier, context); + } + + const ext = extname(specifier); + + // Unless there's an explicit import attribute, JSON files are loaded with our custom loader that's defined below. + if (ext === '.json' && !context.importAttributes?.type) { + const jsonResult = await nextResolve(specifier, context); + return { + ...jsonResult, + format: 'commonjs', + importAttributes: { type: 'json' }, + }; + } + + // Anything else with an explicit extension is handled by the default + // resolver, except that we help determine the module type where needed. + if (ext !== '') { + return withDetectedModuleType(await nextResolve(specifier, context)); + } + + // Other external modules are handled by the default resolver, but again we + // help determine the module type where needed. + if (!specifier.startsWith('.')) { + return withDetectedModuleType(await nextResolve(specifier, context)); + } + + // Imports with exact file extensions are handled by the default resolver + // if (ext !== '') { + // return withDetectedModuleType(await nextResolve(specifier, context)); + // } + + // The rest of this function handles the case of resolving imports that do not + // specify any extension and might point to a directory with an `index.*` + // file. We resolve those using the same logic as most JS bundlers would, with + // the addition of checking if there's an explicit module format listed in the + // closest `package.json` file. + // + // We use a bundle resolution strategy in order to keep code consistent across + // Backstage codebases that contains code both for Web and Node.js, and to + // support packages with common code that can be used in both environments. + try { + // This is expected to throw, but in the event that this module specifier is + // supported we prefer to use the default resolver. + return await nextResolve(specifier, context); + } catch (error) { + if (error.code === 'ERR_UNSUPPORTED_DIR_IMPORT') { + const spec = `${specifier}${specifier.endsWith('/') ? '' : '/'}index`; + const resolved = await resolveWithoutExt(spec, context, nextResolve); + if (resolved) { + return withDetectedModuleType(resolved); + } + } else if (error.code === 'ERR_MODULE_NOT_FOUND') { + const resolved = await resolveWithoutExt(specifier, context, nextResolve); + if (resolved) { + return withDetectedModuleType(resolved); + } + } + + // Unexpected error or no resolution found + throw error; + } +} + +/** + * Populates the `format` field in the resolved object based on the closest `package.json` file. + * + * @param {import('module').ResolveFnOutput} resolved + * @returns {Promise} + */ +async function withDetectedModuleType(resolved) { + // Already has an explicit format + if (resolved.format) { + return resolved; + } + + const ext = extname(resolved.url); + + const explicitFormat = moduleTypeTable[ext]; + if (explicitFormat) { + return { + ...resolved, + format: explicitFormat, + }; + } + + // TODO(Rugvip): Afaik this should never happen and we can remove this check, but want it here for a little while to verify. + if (ext === '.js') { + throw new Error('Unexpected .js file without explicit format'); + } + + // TODO(Rugvip): Does this need caching? kept it simple for now but worth exploring + const packageJsonPath = await findPackageJSON(fileURLToPath(resolved.url)); + if (!packageJsonPath) { + return resolved; + } + + const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf8')); + return { + ...resolved, + format: packageJson.type ?? DEFAULT_MODULE_FORMAT, + }; +} + +/** + * Find the closes package.json file from the given path. + * + * TODO(Rugvip): This can be replaced with the Node.js built-in with the same name once it is stable. + * @param {string} startPath + * @returns {Promise} + */ +async function findPackageJSON(startPath) { + let path = startPath; + + // Some confidence check to avoid infinite loop + for (let i = 0; i < 1000; i++) { + const packagePath = resolvePath(path, 'package.json'); + if (existsSync(packagePath)) { + return packagePath; + } + + const newPath = dirname(path); + if (newPath === path) { + return undefined; + } + path = newPath; + } + + throw new Error( + `Iteration limit reached when searching for package.json at ${startPath}`, + ); +} + +/** @type {import('module').ResolveHook} */ +async function resolveWithoutExt(specifier, context, nextResolve) { + for (const tryExt of EXTS) { + try { + const resolved = await nextResolve(specifier + tryExt, { + ...context, + format: 'commonjs', + }); + return { + ...resolved, + format: moduleTypeTable[tryExt] ?? resolved.format, + }; + } catch { + /* ignore */ + } + } + return undefined; +} + +/** @type {import('module').LoadHook} */ +export async function load(url, context, nextLoad) { + // Non-file URLs are handled by the default loader + if (!url.startsWith('file://')) { + return nextLoad(url, context); + } + + // JSON files loaded as CommonJS are handled by this custom loader, because + // the default one doesn't work. For JSON loading to work we'd need the + // synchronous hooks that aren't supported yet, or avoid using the CommonJS + // compatibility. + if ( + context.format === 'commonjs' && + context.importAttributes?.type === 'json' + ) { + try { + // TODO(Rugvip): Make sure this is valid JSON + const content = await readFile(fileURLToPath(url), 'utf8'); + return { + source: `module.exports = (${content})`, + format: 'commonjs', + shortCircuit: true, + }; + } catch { + // Let the default loader generate the error + return nextLoad(url, context); + } + } + + const ext = extname(url); + + // Non-TS files are handled by the default loader + if (!TS_EXTS.includes(ext)) { + return nextLoad(url, context); + } + + const format = context.format ?? DEFAULT_MODULE_FORMAT; + + // We have two choices at this point, we can either transform CommonJS files + // and return the transformed source code, or let the default loader handle + // them. If we transform them ourselves we will enter CommonJS compatibility + // mode in the new module system in Node.js, this effectively means all + // CommonJS loaded via `require` calls from this point will all be treated as + // if it was loaded via `import` calls from modules. + // + // The CommonJS compatibility layer will try to identify named exports and + // make them available directly, which is convenient as it avoids things like + // `import(...).then(m => m.default.foo)`, allowing you to instead write + // `import(...).then(m => m.foo)`. The compatibility layer doesn't always work + // all that well though, and can lead to module loading issues in many cases, + // especially for older code. + + // This `if` block opts-out of using CommonJS compatibility mode, and instead + // leaves it to our existing loader to transform CommonJS. + // + // TODO(Rugvip): Once the synchronous hooks API is available for us to use, we might be able to adopt that instead + if (format === 'commonjs') { + return nextLoad(url, { ...context, format }); + } + + const transformed = await transformFile(fileURLToPath(url), { + sourceMaps: 'inline', + module: { + type: format === 'module' ? 'nodenext' : 'commonjs', + ignoreDynamic: true, + + // This helps the Node.js CommonJS compat layer identify named exports. + exportInteropAnnotation: true, + }, + jsc: { + target: 'es2022', + parser: { + syntax: 'typescript', + }, + }, + }); + + return { + ...context, + shortCircuit: true, + source: transformed.code, + format, + responseURL: url, + }; +} From 8946eeb43799ed19b3d5f4cec30572b6590437b9 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 22 Dec 2024 11:28:12 +0100 Subject: [PATCH 040/420] cli: updated dynamic command imports to be compatible with module Signed-off-by: Patrik Oldsberg --- packages/cli/src/commands/index.ts | 58 +++++++++++++----------------- packages/cli/src/lib/lazy.ts | 18 ++++++++-- 2 files changed, 39 insertions(+), 37 deletions(-) diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index 0f38fc1e8a..f1119053ec 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -43,7 +43,7 @@ export function registerRepoCommand(program: Command) { '--minify', 'Minify the generated code. Does not apply to app package (app is minified by default).', ) - .action(lazy(() => import('./repo/build').then(m => m.command))); + .action(lazy(() => import('./repo/build'), 'command')); command .command('lint') @@ -70,7 +70,7 @@ export function registerRepoCommand(program: Command) { 'Set the success cache location, (default: node_modules/.cache/backstage-cli)', ) .option('--fix', 'Attempt to automatically fix violations') - .action(lazy(() => import('./repo/lint').then(m => m.command))); + .action(lazy(() => import('./repo/lint'), 'command')); command .command('fix') @@ -83,20 +83,18 @@ export function registerRepoCommand(program: Command) { '--check', 'Fail if any packages would have been changed by the command', ) - .action(lazy(() => import('./repo/fix').then(m => m.command))); + .action(lazy(() => import('./repo/fix'), 'command')); command .command('clean') .description('Delete cache and output directories') - .action(lazy(() => import('./repo/clean').then(m => m.command))); + .action(lazy(() => import('./repo/clean'), 'command')); command .command('list-deprecations') .description('List deprecations') .option('--json', 'Output as JSON') - .action( - lazy(() => import('./repo/list-deprecations').then(m => m.command)), - ); + .action(lazy(() => import('./repo/list-deprecations'), 'command')); command .command('test') @@ -118,7 +116,7 @@ export function registerRepoCommand(program: Command) { 'Show help for Jest CLI options, which are passed through', ) .description('Run tests, forwarding args to Jest, defaulting to watch mode') - .action(lazy(() => import('./repo/test').then(m => m.command))); + .action(lazy(() => import('./repo/test'), 'command')); } export function registerScriptCommand(program: Command) { @@ -139,7 +137,7 @@ export function registerScriptCommand(program: Command) { ) .option('--require ', 'Add a --require argument to the node process') .option('--link ', 'Link an external workspace for module resolution') - .action(lazy(() => import('./start').then(m => m.command))); + .action(lazy(() => import('./start'), 'command')); command .command('build') @@ -163,7 +161,7 @@ export function registerScriptCommand(program: Command) { (opt: string, opts: string[]) => (opts ? [...opts, opt] : [opt]), Array(), ) - .action(lazy(() => import('./build').then(m => m.command))); + .action(lazy(() => import('./build'), 'command')); command .command('lint [directories...]') @@ -182,29 +180,29 @@ export function registerScriptCommand(program: Command) { 'Fail if more than this number of warnings. -1 allows warnings. (default: 0)', ) .description('Lint a package') - .action(lazy(() => import('./lint').then(m => m.default))); + .action(lazy(() => import('./lint'), 'default')); command .command('test') .allowUnknownOption(true) // Allows the command to run, but we still need to parse raw args .helpOption(', --backstage-cli-help') // Let Jest handle help .description('Run tests, forwarding args to Jest, defaulting to watch mode') - .action(lazy(() => import('./test').then(m => m.default))); + .action(lazy(() => import('./test'), 'default')); command .command('clean') .description('Delete cache directories') - .action(lazy(() => import('./clean/clean').then(m => m.default))); + .action(lazy(() => import('./clean/clean'), 'default')); command .command('prepack') .description('Prepares a package for packaging before publishing') - .action(lazy(() => import('./pack').then(m => m.pre))); + .action(lazy(() => import('./pack'), 'pre')); command .command('postpack') .description('Restores the changes made by the prepack command') - .action(lazy(() => import('./pack').then(m => m.post))); + .action(lazy(() => import('./pack'), 'post')); } export function registerMigrateCommand(program: Command) { @@ -215,39 +213,31 @@ export function registerMigrateCommand(program: Command) { command .command('package-roles') .description(`Add package role field to packages that don't have it`) - .action(lazy(() => import('./migrate/packageRole').then(m => m.default))); + .action(lazy(() => import('./migrate/packageRole'), 'default')); command .command('package-scripts') .description('Set package scripts according to each package role') - .action( - lazy(() => import('./migrate/packageScripts').then(m => m.command)), - ); + .action(lazy(() => import('./migrate/packageScripts'), 'command')); command .command('package-exports') .description('Synchronize package subpath export definitions') - .action( - lazy(() => import('./migrate/packageExports').then(m => m.command)), - ); + .action(lazy(() => import('./migrate/packageExports'), 'command')); command .command('package-lint-configs') .description( 'Migrates all packages to use @backstage/cli/config/eslint-factory', ) - .action( - lazy(() => import('./migrate/packageLintConfigs').then(m => m.command)), - ); + .action(lazy(() => import('./migrate/packageLintConfigs'), 'command')); command .command('react-router-deps') .description( 'Migrates the react-router dependencies for all packages to be peer dependencies', ) - .action( - lazy(() => import('./migrate/reactRouterDeps').then(m => m.command)), - ); + .action(lazy(() => import('./migrate/reactRouterDeps'), 'command')); } export function registerCommands(program: Command) { @@ -281,7 +271,7 @@ export function registerCommands(program: Command) { 'The license to use for any new packages (default: Apache-2.0)', ) .option('--no-private', 'Do not mark new packages as private') - .action(lazy(() => import('./new/new').then(m => m.default))); + .action(lazy(() => import('./new/new'), 'default')); registerConfigCommands(program); registerRepoCommand(program); @@ -302,7 +292,7 @@ export function registerCommands(program: Command) { .option('--skip-install', 'Skips yarn install step') .option('--skip-migrate', 'Skips migration of any moved packages') .description('Bump Backstage packages to the latest versions') - .action(lazy(() => import('./versions/bump').then(m => m.default))); + .action(lazy(() => import('./versions/bump'), 'default')); program .command('versions:migrate') @@ -317,7 +307,7 @@ export function registerCommands(program: Command) { .description( 'Migrate any plugins that have been moved to the @backstage-community namespace automatically', ) - .action(lazy(() => import('./versions/migrate').then(m => m.default))); + .action(lazy(() => import('./versions/migrate'), 'default')); program .command('build-workspace [packages...]') @@ -334,17 +324,17 @@ export function registerCommands(program: Command) { 'Force workspace output to be a result of running `yarn pack` on each package (warning: very slow)', ) .description('Builds a temporary dist workspace from the provided packages') - .action(lazy(() => import('./buildWorkspace').then(m => m.default))); + .action(lazy(() => import('./buildWorkspace'), 'default')); program .command('create-github-app ') .description('Create new GitHub App in your organization.') - .action(lazy(() => import('./create-github-app').then(m => m.default))); + .action(lazy(() => import('./create-github-app'), 'default')); program .command('info') .description('Show helpful information for debugging and reporting bugs') - .action(lazy(() => import('./info').then(m => m.default))); + .action(lazy(() => import('./info'), 'default')); // Notifications for removed commands program diff --git a/packages/cli/src/lib/lazy.ts b/packages/cli/src/lib/lazy.ts index 6d2cb1cd4f..d1255ea1bc 100644 --- a/packages/cli/src/lib/lazy.ts +++ b/packages/cli/src/lib/lazy.ts @@ -17,13 +17,25 @@ import { assertError } from '@backstage/errors'; import { exitWithError } from '../lib/errors'; +type ActionFunc = (...args: any[]) => Promise; +type ActionExports = { + [KName in keyof TModule as TModule[KName] extends ActionFunc + ? KName + : never]: TModule[KName]; +}; + // Wraps an action function so that it always exits and handles errors -export function lazy( - getActionFunc: () => Promise<(...args: any[]) => Promise>, +export function lazy( + moduleLoader: () => Promise, + exportName: keyof ActionExports, ): (...args: any[]) => Promise { return async (...args: any[]) => { try { - const actionFunc = await getActionFunc(); + const mod = await moduleLoader(); + const actualModule = ( + mod as unknown as { default: ActionExports } + ).default; + const actionFunc = actualModule[exportName] as ActionFunc; await actionFunc(...args); process.exit(0); From 38023ae743cd33926a9bd38c24630debe0711ad8 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 22 Dec 2024 11:41:11 +0100 Subject: [PATCH 041/420] cli: update alpha entry to support dynamic imports Signed-off-by: Patrik Oldsberg --- packages/cli/src/alpha.ts | 2 +- packages/cli/src/modules/config/alpha.ts | 2 +- packages/cli/src/modules/config/index.ts | 8 +++--- packages/cli/src/wiring/CliInitializer.ts | 32 ++++++++++++++++++++--- 4 files changed, 35 insertions(+), 9 deletions(-) diff --git a/packages/cli/src/alpha.ts b/packages/cli/src/alpha.ts index 4b2cfa4842..f6cd2157ae 100644 --- a/packages/cli/src/alpha.ts +++ b/packages/cli/src/alpha.ts @@ -24,6 +24,6 @@ import chalk from 'chalk'; ), ); const initializer = new CliInitializer(); - initializer.add(import('./modules/config/alpha').then(m => m.default)); + initializer.add(import('./modules/config/alpha')); await initializer.run(); })(); diff --git a/packages/cli/src/modules/config/alpha.ts b/packages/cli/src/modules/config/alpha.ts index c32982086a..9ce6080a4a 100644 --- a/packages/cli/src/modules/config/alpha.ts +++ b/packages/cli/src/modules/config/alpha.ts @@ -32,7 +32,7 @@ export default createCliPlugin({ 'Only include the schema that applies to the given package', ) .description('Browse the configuration reference documentation') - .action(lazy(() => import('./commands/docs').then(m => m.default))); + .action(lazy(() => import('./commands/docs'), 'default')); await defaultCommand.parseAsync(args, { from: 'user' }); }, diff --git a/packages/cli/src/modules/config/index.ts b/packages/cli/src/modules/config/index.ts index 0c6a79577d..a1436d0c21 100644 --- a/packages/cli/src/modules/config/index.ts +++ b/packages/cli/src/modules/config/index.ts @@ -32,7 +32,7 @@ export function registerCommands(program: Command) { 'Only include the schema that applies to the given package', ) .description('Browse the configuration reference documentation') - .action(lazy(() => import('./commands/docs').then(m => m.default))); + .action(lazy(() => import('./commands/docs'), 'default')); program .command('config:print') @@ -49,7 +49,7 @@ export function registerCommands(program: Command) { ) .option(...configOption) .description('Print the app configuration for the current package') - .action(lazy(() => import('./commands/print').then(m => m.default))); + .action(lazy(() => import('./commands/print'), 'default')); program .command('config:check') @@ -68,7 +68,7 @@ export function registerCommands(program: Command) { .description( 'Validate that the given configuration loads and matches schema', ) - .action(lazy(() => import('./commands/validate').then(m => m.default))); + .action(lazy(() => import('./commands/validate'), 'default')); program .command('config:schema') @@ -83,5 +83,5 @@ export function registerCommands(program: Command) { .option('--merge', 'Print the config schemas merged', true) .option('--no-merge', 'Print the config schemas not merged') .description('Print configuration schema') - .action(lazy(() => import('./commands/schema').then(m => m.default))); + .action(lazy(() => import('./commands/schema'), 'default')); } diff --git a/packages/cli/src/wiring/CliInitializer.ts b/packages/cli/src/wiring/CliInitializer.ts index 9fe8bb8084..5acb88151f 100644 --- a/packages/cli/src/wiring/CliInitializer.ts +++ b/packages/cli/src/wiring/CliInitializer.ts @@ -22,16 +22,23 @@ import { version } from '../lib/version'; import chalk from 'chalk'; import { exitWithError } from '../lib/errors'; import { assertError } from '@backstage/errors'; +import { isPromise } from 'util/types'; -type UninitializedFeature = CliFeature | Promise; +type UninitializedFeature = CliFeature | Promise<{ default: CliFeature }>; export class CliInitializer { private graph = new CommandGraph(); private commandRegistry = new CommandRegistry(this.graph); #uninitiazedFeatures: Promise[] = []; - add(module: UninitializedFeature) { - this.#uninitiazedFeatures.push(Promise.resolve(module)); + add(feature: UninitializedFeature) { + if (isPromise(feature)) { + this.#uninitiazedFeatures.push( + feature.then(f => unwrapFeature(f.default)), + ); + } else { + this.#uninitiazedFeatures.push(Promise.resolve(feature)); + } } async #register(feature: CliFeature) { @@ -136,3 +143,22 @@ function isCliPlugin(feature: CliFeature): feature is InternalCliPlugin { // Backwards compatibility for v1 registrations that use duck typing return 'plugin' in internal; } + +/** @internal */ +export function unwrapFeature( + feature: CliFeature | { default: CliFeature }, +): CliFeature { + if ('$$type' in feature) { + return feature; + } + + // This is a workaround where default exports get transpiled to `exports['default'] = ...` + // in CommonJS modules, which in turn results in a double `{ default: { default: ... } }` nesting + // when importing using a dynamic import. + // TODO: This is a broader issue than just this piece of code, and should move away from CommonJS. + if ('default' in feature) { + return feature.default; + } + + return feature; +} From cdfff7254dd5e9a388dc1f1e80ac878b081ea1cc Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 22 Dec 2024 12:18:21 +0100 Subject: [PATCH 042/420] cli: initial module transform tests Signed-off-by: Patrik Oldsberg --- .../tests/transforms/__fixtures__/.gitignore | 1 + .../node_modules/dep-commonjs/a-default.js | 1 + .../node_modules/dep-commonjs/a-named.js | 1 + .../node_modules/dep-commonjs/b-default.mjs | 1 + .../node_modules/dep-commonjs/b-named.mjs | 1 + .../node_modules/dep-commonjs/c-default.cjs | 1 + .../node_modules/dep-commonjs/c-named.cjs | 1 + .../node_modules/dep-commonjs/main.js | 14 +++ .../node_modules/dep-commonjs/package.json | 7 ++ .../node_modules/dep-default/a-default.js | 1 + .../node_modules/dep-default/a-named.js | 1 + .../node_modules/dep-default/b-default.mjs | 1 + .../node_modules/dep-default/b-named.mjs | 1 + .../node_modules/dep-default/c-default.cjs | 1 + .../node_modules/dep-default/c-named.cjs | 1 + .../node_modules/dep-default/main.js | 14 +++ .../node_modules/dep-default/package.json | 6 + .../node_modules/dep-module/a-default.js | 1 + .../node_modules/dep-module/a-named.js | 1 + .../node_modules/dep-module/b-default.mjs | 1 + .../node_modules/dep-module/b-named.mjs | 1 + .../node_modules/dep-module/c-default.cjs | 1 + .../node_modules/dep-module/c-named.cjs | 1 + .../node_modules/dep-module/main.js | 14 +++ .../node_modules/dep-module/package.json | 7 ++ .../__fixtures__/pkg-commonjs/a-default.ts | 16 +++ .../__fixtures__/pkg-commonjs/a-named.ts | 16 +++ .../__fixtures__/pkg-commonjs/b-default.mts | 16 +++ .../__fixtures__/pkg-commonjs/b-named.mts | 16 +++ .../__fixtures__/pkg-commonjs/c-default.cts | 16 +++ .../__fixtures__/pkg-commonjs/c-named.cts | 16 +++ .../__fixtures__/pkg-commonjs/main.ts | 70 ++++++++++++ .../__fixtures__/pkg-commonjs/package.json | 7 ++ .../__fixtures__/pkg-default/a-default.ts | 16 +++ .../__fixtures__/pkg-default/a-named.ts | 16 +++ .../__fixtures__/pkg-default/b-default.mts | 16 +++ .../__fixtures__/pkg-default/b-named.mts | 16 +++ .../__fixtures__/pkg-default/c-default.cts | 16 +++ .../__fixtures__/pkg-default/c-named.cts | 16 +++ .../__fixtures__/pkg-default/main.ts | 70 ++++++++++++ .../__fixtures__/pkg-default/package.json | 3 + .../__fixtures__/pkg-module/a-default.ts | 16 +++ .../__fixtures__/pkg-module/a-named.ts | 16 +++ .../__fixtures__/pkg-module/b-default.mts | 16 +++ .../__fixtures__/pkg-module/b-named.mts | 16 +++ .../__fixtures__/pkg-module/c-default.cts | 16 +++ .../__fixtures__/pkg-module/c-named.cts | 16 +++ .../__fixtures__/pkg-module/main.ts | 73 +++++++++++++ .../__fixtures__/pkg-module/package.json | 7 ++ .../src/tests/transforms/transforms.test.ts | 103 ++++++++++++++++++ 50 files changed, 702 insertions(+) create mode 100644 packages/cli/src/tests/transforms/__fixtures__/.gitignore create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/a-default.js create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/a-named.js create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/b-default.mjs create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/b-named.mjs create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/c-default.cjs create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/c-named.cjs create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/main.js create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/package.json create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/a-default.js create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/a-named.js create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/b-default.mjs create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/b-named.mjs create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/c-default.cjs create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/c-named.cjs create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/main.js create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/package.json create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/a-default.js create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/a-named.js create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/b-default.mjs create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/b-named.mjs create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/c-default.cjs create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/c-named.cjs create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/main.js create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/package.json create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/a-default.ts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/a-named.ts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/b-default.mts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/b-named.mts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/c-default.cts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/c-named.cts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/main.ts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/package.json create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-default/a-default.ts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-default/a-named.ts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-default/b-default.mts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-default/b-named.mts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-default/c-default.cts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-default/c-named.cts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-default/main.ts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-default/package.json create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-default.ts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-named.ts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-module/b-default.mts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-module/b-named.mts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-module/c-default.cts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-module/c-named.cts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-module/package.json create mode 100644 packages/cli/src/tests/transforms/transforms.test.ts diff --git a/packages/cli/src/tests/transforms/__fixtures__/.gitignore b/packages/cli/src/tests/transforms/__fixtures__/.gitignore new file mode 100644 index 0000000000..cf4bab9ddd --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/.gitignore @@ -0,0 +1 @@ +!node_modules diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/a-default.js b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/a-default.js new file mode 100644 index 0000000000..67606e3f86 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/a-default.js @@ -0,0 +1 @@ +module.exports = 'a' diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/a-named.js b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/a-named.js new file mode 100644 index 0000000000..0710f9dbe6 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/a-named.js @@ -0,0 +1 @@ +exports.value = 'a' diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/b-default.mjs b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/b-default.mjs new file mode 100644 index 0000000000..a3bb49043e --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/b-default.mjs @@ -0,0 +1 @@ +export default 'b' diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/b-named.mjs b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/b-named.mjs new file mode 100644 index 0000000000..18049c8488 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/b-named.mjs @@ -0,0 +1 @@ +export const value = 'b' diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/c-default.cjs b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/c-default.cjs new file mode 100644 index 0000000000..7212f4d5a7 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/c-default.cjs @@ -0,0 +1 @@ +module.exports = 'c' diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/c-named.cjs b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/c-named.cjs new file mode 100644 index 0000000000..c1dcb4b923 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/c-named.cjs @@ -0,0 +1 @@ +exports.value = 'c' diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/main.js b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/main.js new file mode 100644 index 0000000000..d59f7789bd --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/main.js @@ -0,0 +1,14 @@ +exports.namedA = require('./a-named').value; +// exports.namedB = require('./b-named.mjs').value; +exports.namedC = require('./c-named.cjs').value; +exports.defaultA = require('./a-default'); +// exports.defaultB = require('./b-default.mjs').default; +exports.defaultC = require('./c-default.cjs'); +exports.dyn = { + namedA: import('./a-named').then(m => m.value), + namedB: import('./b-named.mjs').then(m => m.value), + namedC: import('./c-named.cjs').then(m => m.value), + defaultA: import('./a-default').then(m => m.default), + defaultB: import('./b-default.mjs').then(m => m.default), + defaultC: import('./c-default.cjs').then(m => m.default), +} diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/package.json b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/package.json new file mode 100644 index 0000000000..348dc7e258 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/package.json @@ -0,0 +1,7 @@ +{ + "name": "dep-commonjs", + "type": "commonjs", + "exports": { + ".": "./main.js" + } +} diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/a-default.js b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/a-default.js new file mode 100644 index 0000000000..67606e3f86 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/a-default.js @@ -0,0 +1 @@ +module.exports = 'a' diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/a-named.js b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/a-named.js new file mode 100644 index 0000000000..0710f9dbe6 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/a-named.js @@ -0,0 +1 @@ +exports.value = 'a' diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/b-default.mjs b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/b-default.mjs new file mode 100644 index 0000000000..a3bb49043e --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/b-default.mjs @@ -0,0 +1 @@ +export default 'b' diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/b-named.mjs b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/b-named.mjs new file mode 100644 index 0000000000..18049c8488 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/b-named.mjs @@ -0,0 +1 @@ +export const value = 'b' diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/c-default.cjs b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/c-default.cjs new file mode 100644 index 0000000000..7212f4d5a7 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/c-default.cjs @@ -0,0 +1 @@ +module.exports = 'c' diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/c-named.cjs b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/c-named.cjs new file mode 100644 index 0000000000..c1dcb4b923 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/c-named.cjs @@ -0,0 +1 @@ +exports.value = 'c' diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/main.js b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/main.js new file mode 100644 index 0000000000..d59f7789bd --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/main.js @@ -0,0 +1,14 @@ +exports.namedA = require('./a-named').value; +// exports.namedB = require('./b-named.mjs').value; +exports.namedC = require('./c-named.cjs').value; +exports.defaultA = require('./a-default'); +// exports.defaultB = require('./b-default.mjs').default; +exports.defaultC = require('./c-default.cjs'); +exports.dyn = { + namedA: import('./a-named').then(m => m.value), + namedB: import('./b-named.mjs').then(m => m.value), + namedC: import('./c-named.cjs').then(m => m.value), + defaultA: import('./a-default').then(m => m.default), + defaultB: import('./b-default.mjs').then(m => m.default), + defaultC: import('./c-default.cjs').then(m => m.default), +} diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/package.json b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/package.json new file mode 100644 index 0000000000..1543cf8010 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/package.json @@ -0,0 +1,6 @@ +{ + "name": "dep-default", + "exports": { + ".": "./main.js" + } +} diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/a-default.js b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/a-default.js new file mode 100644 index 0000000000..90bd54cd7f --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/a-default.js @@ -0,0 +1 @@ +export default 'a' diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/a-named.js b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/a-named.js new file mode 100644 index 0000000000..7fea2538a3 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/a-named.js @@ -0,0 +1 @@ +export const value = 'a' diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/b-default.mjs b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/b-default.mjs new file mode 100644 index 0000000000..a3bb49043e --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/b-default.mjs @@ -0,0 +1 @@ +export default 'b' diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/b-named.mjs b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/b-named.mjs new file mode 100644 index 0000000000..18049c8488 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/b-named.mjs @@ -0,0 +1 @@ +export const value = 'b' diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/c-default.cjs b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/c-default.cjs new file mode 100644 index 0000000000..7212f4d5a7 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/c-default.cjs @@ -0,0 +1 @@ +module.exports = 'c' diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/c-named.cjs b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/c-named.cjs new file mode 100644 index 0000000000..c1dcb4b923 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/c-named.cjs @@ -0,0 +1 @@ +exports.value = 'c' diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/main.js b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/main.js new file mode 100644 index 0000000000..8b42a09d4a --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/main.js @@ -0,0 +1,14 @@ +export { value as namedA } from './a-named' +export { value as namedB } from './b-named.mjs' +export { value as namedC } from './c-named.cjs' +export { default as defaultA } from './a-default' +export { default as defaultB } from './b-default.mjs' +export { default as defaultC } from './c-default.cjs' +export const dyn = { + namedA: import('./a-named').then(m => m.value), + namedB: import('./b-named.mjs').then(m => m.value), + namedC: import('./c-named.cjs').then(m => m.value), + defaultA: import('./a-default').then(m => m.default), + defaultB: import('./b-default.mjs').then(m => m.default), + defaultC: import('./c-default.cjs').then(m => m.default), +} diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/package.json b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/package.json new file mode 100644 index 0000000000..b28b4b6562 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/package.json @@ -0,0 +1,7 @@ +{ + "name": "dep-module", + "type": "module", + "exports": { + ".": "./main.js" + } +} diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/a-default.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/a-default.ts new file mode 100644 index 0000000000..a3d64880a9 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/a-default.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export default 'a'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/a-named.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/a-named.ts new file mode 100644 index 0000000000..132570f2bb --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/a-named.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const value = 'a'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/b-default.mts b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/b-default.mts new file mode 100644 index 0000000000..57d07f2c30 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/b-default.mts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export default 'b'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/b-named.mts b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/b-named.mts new file mode 100644 index 0000000000..be9bb39348 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/b-named.mts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const value = 'b'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/c-default.cts b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/c-default.cts new file mode 100644 index 0000000000..48f5b4f136 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/c-default.cts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export default 'c'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/c-named.cts b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/c-named.cts new file mode 100644 index 0000000000..c3e5b58572 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/c-named.cts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const value = 'c'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/main.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/main.ts new file mode 100644 index 0000000000..4cb18038b3 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/main.ts @@ -0,0 +1,70 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-nocheck + +import * as depCommonJs from 'dep-commonjs'; +// import * as depModule from 'dep-module'; +import * as depDefault from 'dep-default'; +import { value as namedA } from './a-named'; +// import { value as namedB } from './b-named'; +import { value as namedC } from './c-named'; +import { default as defaultA } from './a-default'; +// import { default as defaultB } from './b-default'; +import { default as defaultC } from './c-default'; + +async function resolveAll(obj) { + const val = await obj; + if (typeof val !== 'object' || val === null) { + return val; + } + if (Array.isArray(val)) { + return await Promise.all(val.map(resolveAll)); + } + return Object.fromEntries( + await Promise.all( + Object.entries(obj).map(async ([key, value]) => [ + key, + await resolveAll(await value), + ]), + ), + ); +} + +resolveAll({ + depCommonJs, + // depModule, + depDefault, + dynCommonJs: import('dep-commonjs'), + dynModule: import('dep-module'), + dynDefault: import('dep-default'), + dep: { + namedA, + // namedB, + namedC, + defaultA, + // defaultB, + defaultC, + }, + dyn: { + namedA: import('./a-named').then(m => m.default.value), + namedB: import('./b-named').then(m => m.value), + namedC: import('./c-named').then(m => m.default.value), + defaultA: import('./a-default').then(m => m.default.default), + defaultB: import('./b-default').then(m => m.default), + defaultC: import('./c-default').then(m => m.default.default), + }, +}).then(obj => console.log(JSON.stringify(obj, null, 2))); diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/package.json b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/package.json new file mode 100644 index 0000000000..c622a8ba4d --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/package.json @@ -0,0 +1,7 @@ +{ + "name": "pkg-commonjs", + "type": "commonjs", + "exports": { + ".": "./main.ts" + } +} diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/a-default.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/a-default.ts new file mode 100644 index 0000000000..a3d64880a9 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/a-default.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export default 'a'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/a-named.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/a-named.ts new file mode 100644 index 0000000000..132570f2bb --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/a-named.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const value = 'a'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/b-default.mts b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/b-default.mts new file mode 100644 index 0000000000..57d07f2c30 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/b-default.mts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export default 'b'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/b-named.mts b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/b-named.mts new file mode 100644 index 0000000000..be9bb39348 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/b-named.mts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const value = 'b'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/c-default.cts b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/c-default.cts new file mode 100644 index 0000000000..48f5b4f136 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/c-default.cts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export default 'c'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/c-named.cts b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/c-named.cts new file mode 100644 index 0000000000..c3e5b58572 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/c-named.cts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const value = 'c'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/main.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/main.ts new file mode 100644 index 0000000000..4cb18038b3 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/main.ts @@ -0,0 +1,70 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-nocheck + +import * as depCommonJs from 'dep-commonjs'; +// import * as depModule from 'dep-module'; +import * as depDefault from 'dep-default'; +import { value as namedA } from './a-named'; +// import { value as namedB } from './b-named'; +import { value as namedC } from './c-named'; +import { default as defaultA } from './a-default'; +// import { default as defaultB } from './b-default'; +import { default as defaultC } from './c-default'; + +async function resolveAll(obj) { + const val = await obj; + if (typeof val !== 'object' || val === null) { + return val; + } + if (Array.isArray(val)) { + return await Promise.all(val.map(resolveAll)); + } + return Object.fromEntries( + await Promise.all( + Object.entries(obj).map(async ([key, value]) => [ + key, + await resolveAll(await value), + ]), + ), + ); +} + +resolveAll({ + depCommonJs, + // depModule, + depDefault, + dynCommonJs: import('dep-commonjs'), + dynModule: import('dep-module'), + dynDefault: import('dep-default'), + dep: { + namedA, + // namedB, + namedC, + defaultA, + // defaultB, + defaultC, + }, + dyn: { + namedA: import('./a-named').then(m => m.default.value), + namedB: import('./b-named').then(m => m.value), + namedC: import('./c-named').then(m => m.default.value), + defaultA: import('./a-default').then(m => m.default.default), + defaultB: import('./b-default').then(m => m.default), + defaultC: import('./c-default').then(m => m.default.default), + }, +}).then(obj => console.log(JSON.stringify(obj, null, 2))); diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/package.json b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/package.json new file mode 100644 index 0000000000..9881bbf3a8 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/package.json @@ -0,0 +1,3 @@ +{ + "name": "pkg-default" +} diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-default.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-default.ts new file mode 100644 index 0000000000..a3d64880a9 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-default.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export default 'a'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-named.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-named.ts new file mode 100644 index 0000000000..132570f2bb --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-named.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const value = 'a'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/b-default.mts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/b-default.mts new file mode 100644 index 0000000000..57d07f2c30 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/b-default.mts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export default 'b'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/b-named.mts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/b-named.mts new file mode 100644 index 0000000000..be9bb39348 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/b-named.mts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const value = 'b'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/c-default.cts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/c-default.cts new file mode 100644 index 0000000000..48f5b4f136 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/c-default.cts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export default 'c'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/c-named.cts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/c-named.cts new file mode 100644 index 0000000000..c3e5b58572 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/c-named.cts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const value = 'c'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts new file mode 100644 index 0000000000..4db74f0b86 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts @@ -0,0 +1,73 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-nocheck + +import * as depCommonJs from 'dep-commonjs'; +import * as depModule from 'dep-module'; +import * as depDefault from 'dep-default'; +import { value as namedA } from './a-named'; +import { value as namedB } from './b-named'; +import cNamed from './c-named'; +import defaultA from './a-default'; +import defaultB from './b-default'; +import cDefault from './c-default'; + +const { default: defaultC } = cDefault; +const { value: namedC } = cNamed; + +async function resolveAll(obj) { + const val = await obj; + if (typeof val !== 'object' || val === null) { + return val; + } + if (Array.isArray(val)) { + return await Promise.all(val.map(resolveAll)); + } + return Object.fromEntries( + await Promise.all( + Object.entries(obj).map(async ([key, value]) => [ + key, + await resolveAll(await value), + ]), + ), + ); +} + +resolveAll({ + depCommonJs, + depModule, + depDefault, + dynCommonJs: import('dep-commonjs'), + dynModule: import('dep-module'), + dynDefault: import('dep-default'), + dep: { + namedA, + namedB, + namedC, + defaultA, + defaultB, + defaultC, + }, + dyn: { + namedA: import('./a-named').then(m => m.value), + namedB: import('./b-named').then(m => m.value), + namedC: import('./c-named').then(m => m.default.value), + defaultA: import('./a-default').then(m => m.default), + defaultB: import('./b-default').then(m => m.default), + defaultC: import('./c-default').then(m => m.default.default), + }, +}).then(obj => console.log(JSON.stringify(obj, null, 2))); diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/package.json b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/package.json new file mode 100644 index 0000000000..a04ea067a6 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/package.json @@ -0,0 +1,7 @@ +{ + "name": "pkg-module", + "type": "module", + "exports": { + ".": "./main.ts" + } +} diff --git a/packages/cli/src/tests/transforms/transforms.test.ts b/packages/cli/src/tests/transforms/transforms.test.ts new file mode 100644 index 0000000000..b9180e69d6 --- /dev/null +++ b/packages/cli/src/tests/transforms/transforms.test.ts @@ -0,0 +1,103 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { execFileSync } from 'child_process'; +import { resolve as resolvePath } from 'path'; + +const exportValues = { + all: { + namedA: 'a', + namedB: 'b', + namedC: 'c', + defaultA: 'a', + defaultB: 'b', + defaultC: 'c', + }, + commonJs: { + namedA: 'a', + namedC: 'c', + defaultA: 'a', + defaultC: 'c', + }, +}; + +const expectedExports = { + commonJs: { + ...exportValues.commonJs, + dyn: exportValues.all, + default: { + ...exportValues.commonJs, + dyn: exportValues.all, + }, + }, + module: { + ...exportValues.all, + dyn: exportValues.all, + }, +}; + +function loadFixture(fixture: string) { + return JSON.parse( + execFileSync( + 'node', + [ + '--import', + '@backstage/cli/config/nodeTransform.cjs', + resolvePath(__dirname, `__fixtures__/${fixture}`), + ], + { encoding: 'utf8' }, + ), + ); +} + +describe('node runtime module transforms', () => { + it('should load from commonjs format', async () => { + expect(loadFixture('pkg-commonjs/main.ts')).toEqual({ + depCommonJs: expectedExports.commonJs, + depDefault: expectedExports.commonJs, + dynCommonJs: expectedExports.commonJs, + dynDefault: expectedExports.commonJs, + dynModule: expectedExports.module, + dep: exportValues.commonJs, + dyn: exportValues.all, + }); + }); + + it('should load from default format', async () => { + expect(loadFixture('pkg-default/main.ts')).toEqual({ + depCommonJs: expectedExports.commonJs, + depDefault: expectedExports.commonJs, + dynCommonJs: expectedExports.commonJs, + dynDefault: expectedExports.commonJs, + dynModule: expectedExports.module, + dep: exportValues.commonJs, + dyn: exportValues.all, + }); + }); + + it('should load from module format', async () => { + expect(loadFixture('pkg-module/main.ts')).toEqual({ + depCommonJs: expectedExports.commonJs, + depDefault: expectedExports.commonJs, + depModule: expectedExports.module, + dynCommonJs: expectedExports.commonJs, + dynDefault: expectedExports.commonJs, + dynModule: expectedExports.module, + dep: exportValues.all, + dyn: exportValues.all, + }); + }); +}); From ceda4097bccedaa3c7c95e95c7cb040330bd8243 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 22 Dec 2024 12:20:25 +0100 Subject: [PATCH 043/420] repo-tools: update to work with new dynamic imports Signed-off-by: Patrik Oldsberg --- packages/repo-tools/src/commands/index.ts | 93 +++++++------------ .../commands/repo/schema/openapi/verify.ts | 2 +- 2 files changed, 37 insertions(+), 58 deletions(-) diff --git a/packages/repo-tools/src/commands/index.ts b/packages/repo-tools/src/commands/index.ts index e3c4c9e41f..bb9fbf5087 100644 --- a/packages/repo-tools/src/commands/index.ts +++ b/packages/repo-tools/src/commands/index.ts @@ -39,9 +39,7 @@ function registerPackageCommand(program: Command) { 'Initialize any required files to use the OpenAPI tooling for this package.', ) .action( - lazy(() => - import('./package/schema/openapi/init').then(m => m.singleCommand), - ), + lazy(() => import('./package/schema/openapi/init'), 'singleCommand'), ); openApiCommand @@ -60,11 +58,7 @@ function registerPackageCommand(program: Command) { ) .option('--watch') .description('Watch the OpenAPI spec for changes and regenerate on save.') - .action( - lazy(() => - import('./package/schema/openapi/generate').then(m => m.command), - ), - ); + .action(lazy(() => import('./package/schema/openapi/generate'), 'command')); openApiCommand .command('fuzz') @@ -81,18 +75,14 @@ function registerPackageCommand(program: Command) { '--exclude-checks ', 'Exclude checks from schemathesis run', ) - .action( - lazy(() => import('./package/schema/openapi/fuzz').then(m => m.command)), - ); + .action(lazy(() => import('./package/schema/openapi/fuzz'), 'command')); openApiCommand .command('diff') .option('--ignore', 'Ignore linting failures and only log the results.') .option('--json', 'Output the results as JSON') .option('--since ', 'Diff the API against a specific ref') - .action( - lazy(() => import('./package/schema/openapi/diff').then(m => m.command)), - ); + .action(lazy(() => import('./package/schema/openapi/diff'), 'command')); } function registerRepoCommand(program: Command) { @@ -113,11 +103,7 @@ function registerRepoCommand(program: Command) { .description( 'Verify that all OpenAPI schemas are valid and set up correctly.', ) - .action( - lazy(() => - import('./repo/schema/openapi/verify').then(m => m.bulkCommand), - ), - ); + .action(lazy(() => import('./repo/schema/openapi/verify'), 'bulkCommand')); openApiCommand .command('lint [paths...]') @@ -126,17 +112,13 @@ function registerRepoCommand(program: Command) { '--strict', 'Fail on any linting severity messages, not just errors.', ) - .action( - lazy(() => import('./repo/schema/openapi/lint').then(m => m.bulkCommand)), - ); + .action(lazy(() => import('./repo/schema/openapi/lint'), 'bulkCommand')); openApiCommand .command('test [paths...]') .description('Test OpenAPI schemas against written tests') .option('--update', 'Update the spec on failure.') - .action( - lazy(() => import('./repo/schema/openapi/test').then(m => m.bulkCommand)), - ); + .action(lazy(() => import('./repo/schema/openapi/test'), 'bulkCommand')); openApiCommand .command('fuzz') @@ -145,9 +127,7 @@ function registerRepoCommand(program: Command) { '--since ', 'Only fuzz packages that have changed since the given ref', ) - .action( - lazy(() => import('./repo/schema/openapi/fuzz').then(m => m.command)), - ); + .action(lazy(() => import('./repo/schema/openapi/fuzz'), 'command')); openApiCommand .command('diff') @@ -159,9 +139,7 @@ function registerRepoCommand(program: Command) { 'Diff the API against a specific ref', 'origin/master', ) - .action( - lazy(() => import('./repo/schema/openapi/diff').then(m => m.command)), - ); + .action(lazy(() => import('./repo/schema/openapi/diff'), 'command')); } function registerLintCommand(program: Command) { @@ -174,10 +152,10 @@ function registerLintCommand(program: Command) { 'Lint backend plugin packages for legacy exports and make sure it conforms to the new export pattern', ) .action( - lazy(() => - import( - './lint-legacy-backend-exports/lint-legacy-backend-exports' - ).then(m => m.lint), + lazy( + () => + import('./lint-legacy-backend-exports/lint-legacy-backend-exports'), + 'lint', ), ); } @@ -215,16 +193,12 @@ export function registerCommands(program: Command) { 'Turn on release tag validation for the public, beta, and alpha APIs', ) .description('Generate an API report for selected packages') - .action( - lazy(() => - import('./api-reports/api-reports').then(m => m.buildApiReports), - ), - ); + .action(lazy(() => import('./api-reports/api-reports'), 'buildApiReports')); program .command('type-deps') .description('Find inconsistencies in types of all packages and plugins') - .action(lazy(() => import('./type-deps/type-deps').then(m => m.default))); + .action(lazy(() => import('./type-deps/type-deps'), 'default')); program .command('peer-deps') @@ -232,7 +206,7 @@ export function registerCommands(program: Command) { 'Ensure your packages are using the correct peer dependency format.', ) .option('--fix', 'Fix the issues found') - .action(lazy(() => import('./peer-deps/peer-deps').then(m => m.default))); + .action(lazy(() => import('./peer-deps/peer-deps'), 'default')); program .command('generate-catalog-info') @@ -246,10 +220,9 @@ export function registerCommands(program: Command) { ) .description('Create or fix info yaml files for all backstage packages') .action( - lazy(() => - import('./generate-catalog-info/generate-catalog-info').then( - m => m.default, - ), + lazy( + () => import('./generate-catalog-info/generate-catalog-info'), + 'default', ), ); @@ -278,20 +251,14 @@ export function registerCommands(program: Command) { .description( 'Generate a patch for the selected package in the target repository', ) - .action( - lazy(() => - import('./generate-patch/generate-patch').then(m => m.default), - ), - ); + .action(lazy(() => import('./generate-patch/generate-patch'), 'default')); program .command('knip-reports [paths...]') .option('--ci', 'CI run checks that there is no changes on knip reports') .description('Generate a knip report for selected packages') .action( - lazy(() => - import('./knip-reports/knip-reports').then(m => m.buildKnipReports), - ), + lazy(() => import('./knip-reports/knip-reports'), 'buildKnipReports'), ); registerPackageCommand(program); @@ -299,13 +266,25 @@ export function registerCommands(program: Command) { registerLintCommand(program); } +type ActionFunc = (...args: any[]) => Promise; +type ActionExports = { + [KName in keyof TModule as TModule[KName] extends ActionFunc + ? KName + : never]: TModule[KName]; +}; + // Wraps an action function so that it always exits and handles errors -function lazy( - getActionFunc: () => Promise<(...args: any[]) => Promise>, +export function lazy( + moduleLoader: () => Promise, + exportName: keyof ActionExports, ): (...args: any[]) => Promise { return async (...args: any[]) => { try { - const actionFunc = await getActionFunc(); + const mod = await moduleLoader(); + const actualModule = ( + mod as unknown as { default: ActionExports } + ).default; + const actionFunc = actualModule[exportName] as ActionFunc; await actionFunc(...args); process.exit(0); diff --git a/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts b/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts index 61aadc5d0c..9eab1c77f5 100644 --- a/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts +++ b/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts @@ -54,7 +54,7 @@ async function verify(directoryPath: string) { schemaPath = join(directoryPath, OLD_SCHEMA_PATH); } - const schema = await import(resolvePath(schemaPath)); + const { default: schema } = await import(resolvePath(schemaPath)); if (!schema.spec) { throw new Error(`\`${TS_SCHEMA_PATH}\` needs to have a 'spec' export.`); From f01b5739e6fe2b67abca8bb044387e86b5fb767d Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 23 Dec 2024 10:01:42 +0100 Subject: [PATCH 044/420] cli: refactor transform tests to separate out printing Signed-off-by: Patrik Oldsberg --- .../__fixtures__/pkg-commonjs/main.ts | 6 +++--- .../__fixtures__/pkg-commonjs/print.ts | 19 +++++++++++++++++++ .../__fixtures__/pkg-default/main.ts | 6 +++--- .../__fixtures__/pkg-default/print.ts | 19 +++++++++++++++++++ .../__fixtures__/pkg-module/main.ts | 6 +++--- .../__fixtures__/pkg-module/print.ts | 19 +++++++++++++++++++ .../src/tests/transforms/transforms.test.ts | 6 +++--- 7 files changed, 69 insertions(+), 12 deletions(-) create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/print.ts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-default/print.ts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-module/print.ts diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/main.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/main.ts index 4cb18038b3..c0494e86b6 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/main.ts +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/main.ts @@ -26,7 +26,7 @@ import { default as defaultA } from './a-default'; // import { default as defaultB } from './b-default'; import { default as defaultC } from './c-default'; -async function resolveAll(obj) { +async function resolveAll(obj): Promise { const val = await obj; if (typeof val !== 'object' || val === null) { return val; @@ -44,7 +44,7 @@ async function resolveAll(obj) { ); } -resolveAll({ +export const values = resolveAll({ depCommonJs, // depModule, depDefault, @@ -67,4 +67,4 @@ resolveAll({ defaultB: import('./b-default').then(m => m.default), defaultC: import('./c-default').then(m => m.default.default), }, -}).then(obj => console.log(JSON.stringify(obj, null, 2))); +}); diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/print.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/print.ts new file mode 100644 index 0000000000..7c24f99f37 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/print.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { values } from './main'; + +values.then(obj => console.log(JSON.stringify(obj, null, 2))); diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/main.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/main.ts index 4cb18038b3..c0494e86b6 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/main.ts +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/main.ts @@ -26,7 +26,7 @@ import { default as defaultA } from './a-default'; // import { default as defaultB } from './b-default'; import { default as defaultC } from './c-default'; -async function resolveAll(obj) { +async function resolveAll(obj): Promise { const val = await obj; if (typeof val !== 'object' || val === null) { return val; @@ -44,7 +44,7 @@ async function resolveAll(obj) { ); } -resolveAll({ +export const values = resolveAll({ depCommonJs, // depModule, depDefault, @@ -67,4 +67,4 @@ resolveAll({ defaultB: import('./b-default').then(m => m.default), defaultC: import('./c-default').then(m => m.default.default), }, -}).then(obj => console.log(JSON.stringify(obj, null, 2))); +}); diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/print.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/print.ts new file mode 100644 index 0000000000..7c24f99f37 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/print.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { values } from './main'; + +values.then(obj => console.log(JSON.stringify(obj, null, 2))); diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts index 4db74f0b86..7b40a259f6 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts @@ -29,7 +29,7 @@ import cDefault from './c-default'; const { default: defaultC } = cDefault; const { value: namedC } = cNamed; -async function resolveAll(obj) { +async function resolveAll(obj): Promise { const val = await obj; if (typeof val !== 'object' || val === null) { return val; @@ -47,7 +47,7 @@ async function resolveAll(obj) { ); } -resolveAll({ +export const values = resolveAll({ depCommonJs, depModule, depDefault, @@ -70,4 +70,4 @@ resolveAll({ defaultB: import('./b-default').then(m => m.default), defaultC: import('./c-default').then(m => m.default.default), }, -}).then(obj => console.log(JSON.stringify(obj, null, 2))); +}); diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/print.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/print.ts new file mode 100644 index 0000000000..7c24f99f37 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/print.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { values } from './main'; + +values.then(obj => console.log(JSON.stringify(obj, null, 2))); diff --git a/packages/cli/src/tests/transforms/transforms.test.ts b/packages/cli/src/tests/transforms/transforms.test.ts index b9180e69d6..83676d4136 100644 --- a/packages/cli/src/tests/transforms/transforms.test.ts +++ b/packages/cli/src/tests/transforms/transforms.test.ts @@ -65,7 +65,7 @@ function loadFixture(fixture: string) { describe('node runtime module transforms', () => { it('should load from commonjs format', async () => { - expect(loadFixture('pkg-commonjs/main.ts')).toEqual({ + expect(loadFixture('pkg-commonjs/print.ts')).toEqual({ depCommonJs: expectedExports.commonJs, depDefault: expectedExports.commonJs, dynCommonJs: expectedExports.commonJs, @@ -77,7 +77,7 @@ describe('node runtime module transforms', () => { }); it('should load from default format', async () => { - expect(loadFixture('pkg-default/main.ts')).toEqual({ + expect(loadFixture('pkg-default/print.ts')).toEqual({ depCommonJs: expectedExports.commonJs, depDefault: expectedExports.commonJs, dynCommonJs: expectedExports.commonJs, @@ -89,7 +89,7 @@ describe('node runtime module transforms', () => { }); it('should load from module format', async () => { - expect(loadFixture('pkg-module/main.ts')).toEqual({ + expect(loadFixture('pkg-module/print.ts')).toEqual({ depCommonJs: expectedExports.commonJs, depDefault: expectedExports.commonJs, depModule: expectedExports.module, From 47407fc34c9bf6a60565e050a393ac888737bcd4 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 23 Dec 2024 11:33:44 +0100 Subject: [PATCH 045/420] cli: add ESM support to Jest config + tests Signed-off-by: Patrik Oldsberg --- .github/workflows/ci.yml | 2 +- .github/workflows/verify_windows.yml | 2 +- package.json | 10 +- packages/cli/config/jest.js | 160 +++++++++++------- packages/cli/config/jestSwcTransform.js | 3 +- packages/cli/config/nodeTransformHooks.mjs | 2 +- .../pkg-module/a-default-explicit.mts | 16 ++ .../pkg-module/a-named-explicit.mts | 16 ++ .../__fixtures__/pkg-module/main-explicit.mts | 73 ++++++++ .../src/tests/transforms/transforms.test.ts | 75 ++++++-- 10 files changed, 278 insertions(+), 81 deletions(-) create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-default-explicit.mts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-named-explicit.mts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/pkg-module/main-explicit.mts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 24e74ff0f6..64926b5e8d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -202,7 +202,7 @@ jobs: env: CI: true - NODE_OPTIONS: --max-old-space-size=8192 --no-node-snapshot + NODE_OPTIONS: --max-old-space-size=8192 --no-node-snapshot --experimental-vm-modules INTEGRATION_TEST_GITHUB_TOKEN: ${{ secrets.INTEGRATION_TEST_GITHUB_TOKEN }} INTEGRATION_TEST_GITLAB_TOKEN: ${{ secrets.INTEGRATION_TEST_GITLAB_TOKEN }} INTEGRATION_TEST_BITBUCKET_TOKEN: ${{ secrets.INTEGRATION_TEST_BITBUCKET_TOKEN }} diff --git a/.github/workflows/verify_windows.yml b/.github/workflows/verify_windows.yml index e3f6959929..76ddd85a6b 100644 --- a/.github/workflows/verify_windows.yml +++ b/.github/workflows/verify_windows.yml @@ -21,7 +21,7 @@ jobs: env: CI: true - NODE_OPTIONS: --max-old-space-size=8192 --no-node-snapshot + NODE_OPTIONS: --max-old-space-size=8192 --no-node-snapshot --experimental-vm-modules INTEGRATION_TEST_GITHUB_TOKEN: ${{ secrets.INTEGRATION_TEST_GITHUB_TOKEN }} INTEGRATION_TEST_GITLAB_TOKEN: ${{ secrets.INTEGRATION_TEST_GITLAB_TOKEN }} INTEGRATION_TEST_BITBUCKET_TOKEN: ${{ secrets.INTEGRATION_TEST_BITBUCKET_TOKEN }} diff --git a/package.json b/package.json index b364b2986b..b17e604617 100644 --- a/package.json +++ b/package.json @@ -49,8 +49,8 @@ "storybook": "yarn ./storybook run storybook", "techdocs-cli": "node scripts/techdocs-cli.js", "techdocs-cli:dev": "cross-env TECHDOCS_CLI_DEV_MODE=true node scripts/techdocs-cli.js", - "test": "NODE_OPTIONS=--no-node-snapshot backstage-cli repo test", - "test:all": "NODE_OPTIONS=--no-node-snapshot backstage-cli repo test --coverage", + "test": "NODE_OPTIONS='--no-node-snapshot --experimental-vm-modules' backstage-cli repo test", + "test:all": "NODE_OPTIONS='--no-node-snapshot --experimental-vm-modules' backstage-cli repo test --coverage", "test:e2e": "NODE_OPTIONS=--no-node-snapshot playwright test", "tsc": "tsc", "tsc:full": "backstage-cli repo clean && tsc --skipLibCheck false --incremental false" @@ -84,6 +84,9 @@ ] }, "prettier": "@backstage/cli/config/prettier", + "jest": { + "rejectFrontendNetworkRequests": true + }, "resolutions": { "@changesets/assemble-release-plan@^6.0.0": "patch:@changesets/assemble-release-plan@npm%3A6.0.0#./.yarn/patches/@changesets-assemble-release-plan-npm-6.0.0-f7b3005037.patch", "@material-ui/pickers@^3.2.10": "patch:@material-ui/pickers@npm%3A3.3.11#./.yarn/patches/@material-ui-pickers-npm-3.3.11-1c8f68ea20.patch", @@ -134,9 +137,6 @@ "sort-package-json": "^2.8.0", "typescript": "~5.2.0" }, - "jest": { - "rejectFrontendNetworkRequests": true - }, "packageManager": "yarn@3.8.1", "engines": { "node": "20 || 22" diff --git a/packages/cli/config/jest.js b/packages/cli/config/jest.js index 45d744d7bc..560c9bf708 100644 --- a/packages/cli/config/jest.js +++ b/packages/cli/config/jest.js @@ -31,6 +31,14 @@ const FRONTEND_ROLES = [ 'frontend-plugin-module', ]; +const NODE_ROLES = [ + 'backend', + 'cli', + 'node-library', + 'backend-plugin', + 'backend-plugin-module', +]; + const envOptions = { oldTests: Boolean(process.env.BACKSTAGE_OLD_TESTS), }; @@ -130,11 +138,97 @@ const transformIgnorePattern = [ ].join('|'); // Provides additional config that's based on the role of the target package -function getRoleConfig(role) { +function getRoleConfig(role, pkgJson) { + // Only Node.js package roles support native ESM modules, frontend and common + // packages are always transpiled to CommonJS. + const moduleOpts = NODE_ROLES.includes(role) + ? { + module: { + ignoreDynamic: true, + exportInteropAnnotation: true, + }, + } + : undefined; + + const transform = { + '\\.(mjs|cjs|js)$': [ + require.resolve('./jestSwcTransform'), + { + ...moduleOpts, + jsc: { + parser: { + syntax: 'ecmascript', + }, + }, + }, + ], + '\\.jsx$': [ + require.resolve('./jestSwcTransform'), + { + jsc: { + parser: { + syntax: 'ecmascript', + jsx: true, + }, + transform: { + react: { + runtime: 'automatic', + }, + }, + }, + }, + ], + '\\.(mts|cts|ts)$': [ + require.resolve('./jestSwcTransform'), + { + ...moduleOpts, + jsc: { + parser: { + syntax: 'typescript', + }, + }, + }, + ], + '\\.tsx$': [ + require.resolve('./jestSwcTransform'), + { + jsc: { + parser: { + syntax: 'typescript', + tsx: true, + }, + transform: { + react: { + runtime: 'automatic', + }, + }, + }, + }, + ], + '\\.(bmp|gif|jpg|jpeg|png|ico|webp|frag|xml|svg|eot|woff|woff2|ttf)$': + require.resolve('./jestFileTransform.js'), + '\\.(yaml)$': require.resolve('./jestYamlTransform'), + }; if (FRONTEND_ROLES.includes(role)) { - return { testEnvironment: require.resolve('jest-environment-jsdom') }; + return { + testEnvironment: require.resolve('jest-environment-jsdom'), + transform, + }; } - return { testEnvironment: require.resolve('jest-environment-node') }; + return { + testEnvironment: require.resolve('jest-environment-node'), + moduleFileExtensions: [...SRC_EXTS, 'json', 'node'], + // Jest doesn't let us dynamically detect type=module per transformed file, + // so we have to assume that if the entry point is ESM, all TS files are + // ESM. + // + // This means you can't switch a package to type=module until all of its + // monorepo dependencies are also type=module or does not contain any .ts + // files. + extensionsToTreatAsEsm: + pkgJson.type === 'module' ? ['.ts', '.mts'] : ['.mts'], + transform, + }; } async function getProjectConfig(targetPath, extraConfig, extraOptions) { @@ -160,64 +254,6 @@ async function getProjectConfig(targetPath, extraConfig, extraOptions) { '\\.(css|less|scss|sss|styl)$': require.resolve('jest-css-modules'), }, - transform: { - '\\.(mjs|cjs|js)$': [ - require.resolve('./jestSwcTransform'), - { - jsc: { - parser: { - syntax: 'ecmascript', - }, - }, - }, - ], - '\\.jsx$': [ - require.resolve('./jestSwcTransform'), - { - jsc: { - parser: { - syntax: 'ecmascript', - jsx: true, - }, - transform: { - react: { - runtime: 'automatic', - }, - }, - }, - }, - ], - '\\.ts$': [ - require.resolve('./jestSwcTransform'), - { - jsc: { - parser: { - syntax: 'typescript', - }, - }, - }, - ], - '\\.tsx$': [ - require.resolve('./jestSwcTransform'), - { - jsc: { - parser: { - syntax: 'typescript', - tsx: true, - }, - transform: { - react: { - runtime: 'automatic', - }, - }, - }, - }, - ], - '\\.(bmp|gif|jpg|jpeg|png|ico|webp|frag|xml|svg|eot|woff|woff2|ttf)$': - require.resolve('./jestFileTransform.js'), - '\\.(yaml)$': require.resolve('./jestYamlTransform'), - }, - // A bit more opinionated testMatch: [`**/*.test.{${SRC_EXTS.join(',')}}`], @@ -226,7 +262,7 @@ async function getProjectConfig(targetPath, extraConfig, extraOptions) { : require.resolve('./jestCachingModuleLoader'), transformIgnorePatterns: [`/node_modules/(?:${transformIgnorePattern})/`], - ...getRoleConfig(pkgJson.backstage?.role), + ...getRoleConfig(pkgJson.backstage?.role, pkgJson), }; options.setupFilesAfterEnv = options.setupFilesAfterEnv || []; diff --git a/packages/cli/config/jestSwcTransform.js b/packages/cli/config/jestSwcTransform.js index 83abacc9b5..203bb73d80 100644 --- a/packages/cli/config/jestSwcTransform.js +++ b/packages/cli/config/jestSwcTransform.js @@ -18,12 +18,13 @@ const { createTransformer: createSwcTransformer } = require('@swc/jest'); const ESM_REGEX = /\b(?:import|export)\b/; function createTransformer(config) { + const useModules = Boolean(config?.module); const swcTransformer = createSwcTransformer({ inputSourceMap: false, ...config, }); const process = (source, filePath, jestOptions) => { - if (filePath.endsWith('.js') && !ESM_REGEX.test(source)) { + if (filePath.endsWith('.js') && (useModules || !ESM_REGEX.test(source))) { return { code: source }; } diff --git a/packages/cli/config/nodeTransformHooks.mjs b/packages/cli/config/nodeTransformHooks.mjs index 5892b18ee4..4a897ca6c7 100644 --- a/packages/cli/config/nodeTransformHooks.mjs +++ b/packages/cli/config/nodeTransformHooks.mjs @@ -257,7 +257,7 @@ export async function load(url, context, nextLoad) { const transformed = await transformFile(fileURLToPath(url), { sourceMaps: 'inline', module: { - type: format === 'module' ? 'nodenext' : 'commonjs', + type: format === 'module' ? 'es6' : 'commonjs', ignoreDynamic: true, // This helps the Node.js CommonJS compat layer identify named exports. diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-default-explicit.mts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-default-explicit.mts new file mode 100644 index 0000000000..a3d64880a9 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-default-explicit.mts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export default 'a'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-named-explicit.mts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-named-explicit.mts new file mode 100644 index 0000000000..132570f2bb --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/a-named-explicit.mts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const value = 'a'; diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main-explicit.mts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main-explicit.mts new file mode 100644 index 0000000000..7f45ea506c --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main-explicit.mts @@ -0,0 +1,73 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-nocheck + +import * as depCommonJs from 'dep-commonjs'; +import * as depModule from 'dep-module'; +import * as depDefault from 'dep-default'; +import { value as namedA } from './a-named-explicit'; +import { value as namedB } from './b-named'; +import cNamed from './c-named'; +import defaultA from './a-default-explicit'; +import defaultB from './b-default'; +import cDefault from './c-default'; + +const { default: defaultC } = cDefault; +const { value: namedC } = cNamed; + +async function resolveAll(obj): Promise { + const val = await obj; + if (typeof val !== 'object' || val === null) { + return val; + } + if (Array.isArray(val)) { + return await Promise.all(val.map(resolveAll)); + } + return Object.fromEntries( + await Promise.all( + Object.entries(obj).map(async ([key, value]) => [ + key, + await resolveAll(await value), + ]), + ), + ); +} + +export const values = resolveAll({ + depCommonJs, + depModule, + depDefault, + dynCommonJs: import('dep-commonjs'), + dynModule: import('dep-module'), + dynDefault: import('dep-default'), + dep: { + namedA, + namedB, + namedC, + defaultA, + defaultB, + defaultC, + }, + dyn: { + namedA: import('./a-named-explicit').then(m => m.value), + namedB: import('./b-named').then(m => m.value), + namedC: import('./c-named').then(m => m.default.value), + defaultA: import('./a-default-explicit').then(m => m.default), + defaultB: import('./b-default').then(m => m.default), + defaultC: import('./c-default').then(m => m.default.default), + }, +}); diff --git a/packages/cli/src/tests/transforms/transforms.test.ts b/packages/cli/src/tests/transforms/transforms.test.ts index 83676d4136..ac67069b77 100644 --- a/packages/cli/src/tests/transforms/transforms.test.ts +++ b/packages/cli/src/tests/transforms/transforms.test.ts @@ -50,17 +50,16 @@ const expectedExports = { }; function loadFixture(fixture: string) { - return JSON.parse( - execFileSync( - 'node', - [ - '--import', - '@backstage/cli/config/nodeTransform.cjs', - resolvePath(__dirname, `__fixtures__/${fixture}`), - ], - { encoding: 'utf8' }, - ), + const output = execFileSync( + 'node', + [ + '--import', + '@backstage/cli/config/nodeTransform.cjs', + resolvePath(__dirname, `__fixtures__/${fixture}`), + ], + { encoding: 'utf8' }, ); + return JSON.parse(output); } describe('node runtime module transforms', () => { @@ -101,3 +100,59 @@ describe('node runtime module transforms', () => { }); }); }); + +describe('Jest runtime module transforms', () => { + it('should load from commonjs format', async () => { + const values = await import('./__fixtures__/pkg-commonjs/main').then( + m => m.values, + ); + expect(values).toEqual({ + depCommonJs: expectedExports.commonJs, + depDefault: expectedExports.commonJs, + dynCommonJs: expectedExports.commonJs, + dynDefault: expectedExports.commonJs, + dynModule: expectedExports.module, + dep: exportValues.commonJs, + dyn: exportValues.all, + }); + }); + + it('should load from default format', async () => { + const values = await import('./__fixtures__/pkg-default/main').then( + m => m.values, + ); + expect(values).toEqual({ + depCommonJs: expectedExports.commonJs, + depDefault: expectedExports.commonJs, + dynCommonJs: expectedExports.commonJs, + dynDefault: expectedExports.commonJs, + dynModule: expectedExports.module, + dep: exportValues.commonJs, + dyn: exportValues.all, + }); + }); + + it('should load from module format', async () => { + // This uses a separate entry point with an explicit .mts extension. This is + // because we can't cleanly switch the Jest behavior based on type=module in + // package.json for .ts files in Jest. If a module type is detected we + // instead need to switch the transforms for the entire Jest project, which + // we can't do for this test. We instead use the explicit .mts extension to + // verify the transform behavior. + + // @ts-expect-error Cannot find module './__fixtures__/pkg-module/main-explicit' or its corresponding type declarations. + const values = await import('./__fixtures__/pkg-module/main-explicit').then( + m => m.values, + ); + expect(values).toEqual({ + depCommonJs: expectedExports.commonJs, + depDefault: expectedExports.commonJs, + depModule: expectedExports.module, + dynCommonJs: expectedExports.commonJs, + dynDefault: expectedExports.commonJs, + dynModule: expectedExports.module, + dep: exportValues.all, + dyn: exportValues.all, + }); + }); +}); From 29a4aa8956938d5d13f5912b71f4ebbe0983c986 Mon Sep 17 00:00:00 2001 From: Gabriel Dugny Date: Tue, 24 Dec 2024 12:58:42 +0100 Subject: [PATCH 046/420] fix(config): add missing parameters to configuration schema Signed-off-by: Gabriel Dugny --- .changeset/light-wasps-unite.md | 6 ++++ .../config.d.ts | 34 +++++++++++++++++++ plugins/techdocs-backend/config.d.ts | 5 +++ 3 files changed, 45 insertions(+) create mode 100644 .changeset/light-wasps-unite.md diff --git a/.changeset/light-wasps-unite.md b/.changeset/light-wasps-unite.md new file mode 100644 index 0000000000..1075633f04 --- /dev/null +++ b/.changeset/light-wasps-unite.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-catalog-backend-module-msgraph': patch +'@backstage/plugin-techdocs-backend': patch +--- + +fix(config): add missing parameters in config schema diff --git a/plugins/catalog-backend-module-msgraph/config.d.ts b/plugins/catalog-backend-module-msgraph/config.d.ts index b93026e1bb..b04c1cd303 100644 --- a/plugins/catalog-backend-module-msgraph/config.d.ts +++ b/plugins/catalog-backend-module-msgraph/config.d.ts @@ -165,6 +165,12 @@ export interface Config { * This can be useful for huge organizations. */ loadPhotos?: boolean; + /** + * The fields to be fetched on query. + * + * E.g. ["id", "displayName", "description"] + */ + select?: string[]; }; group?: { @@ -257,15 +263,38 @@ export interface Config { */ queryMode?: string; user?: { + /** + * The "expand" argument to apply to users. + * + * E.g. "manager". + */ + expand?: string; /** * The filter to apply to extract users. * * E.g. "accountEnabled eq true and userType eq 'member'" */ filter?: string; + /** + * Set to false to not load user photos. + * This can be useful for huge organizations. + */ + loadPhotos?: boolean; + /** + * The fields to be fetched on query. + * + * E.g. ["id", "displayName", "description"] + */ + select?: string[]; }; group?: { + /** + * The "expand" argument to apply to groups. + * + * E.g. "member". + */ + expand?: string; /** * The filter to apply to extract groups. * @@ -284,6 +313,11 @@ export interface Config { * E.g. ["id", "displayName", "description"] */ select?: string[]; + /** + * Whether to ingest groups that are members of the found/filtered/searched groups. + * Default value is `false`. + */ + includeSubGroups?: boolean; }; userGroupMember?: { diff --git a/plugins/techdocs-backend/config.d.ts b/plugins/techdocs-backend/config.d.ts index 4910d8d76e..c220717ddb 100644 --- a/plugins/techdocs-backend/config.d.ts +++ b/plugins/techdocs-backend/config.d.ts @@ -263,6 +263,11 @@ export interface Config { * the credentials belongs to a different project to the bucket. */ projectId?: string; + /** + * (Optional) Location in storage bucket to save files + * If not set, the default location will be the root of the storage bucket + */ + bucketRootPath?: string; }; }; From b9c7cef9ab887430b31db3e6a6f78095253eab34 Mon Sep 17 00:00:00 2001 From: irma12 Date: Tue, 24 Dec 2024 12:04:00 +0100 Subject: [PATCH 047/420] Add Wiz plugin to marketplace Signed-off-by: irma12 --- microsite/data/plugins/wiz.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 microsite/data/plugins/wiz.yaml diff --git a/microsite/data/plugins/wiz.yaml b/microsite/data/plugins/wiz.yaml new file mode 100644 index 0000000000..756f52ee1f --- /dev/null +++ b/microsite/data/plugins/wiz.yaml @@ -0,0 +1,10 @@ +--- +title: Wiz +author: roadie.io +authorUrl: https://github.com/RoadieHQ +category: Monitoring +description: View Wiz issues status in Backstage. +documentation: https://roadie.io/backstage/plugins/wiz/ +iconUrl: https://roadie.io/images/logos/wiz-logo.png +npmPackageName: '@roadiehq/backstage-plugin-wiz' +addedDate: '2024-10-14' From e9b137f7d77f1acd7d746f7a5f17803e21c6afa1 Mon Sep 17 00:00:00 2001 From: irma12 Date: Tue, 24 Dec 2024 13:43:11 +0100 Subject: [PATCH 048/420] Fix the logo url Signed-off-by: irma12 --- microsite/data/plugins/wiz.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsite/data/plugins/wiz.yaml b/microsite/data/plugins/wiz.yaml index 756f52ee1f..1496231dfa 100644 --- a/microsite/data/plugins/wiz.yaml +++ b/microsite/data/plugins/wiz.yaml @@ -5,6 +5,6 @@ authorUrl: https://github.com/RoadieHQ category: Monitoring description: View Wiz issues status in Backstage. documentation: https://roadie.io/backstage/plugins/wiz/ -iconUrl: https://roadie.io/images/logos/wiz-logo.png +iconUrl: https://roadie.io/images/wiz-logo.png npmPackageName: '@roadiehq/backstage-plugin-wiz' addedDate: '2024-10-14' From 0be20ed46a91ce3110302b5dccf0a145e875ef5e Mon Sep 17 00:00:00 2001 From: Cory Steers Date: Fri, 20 Dec 2024 11:53:10 -0600 Subject: [PATCH 049/420] Provide additional information regarding proxy settings for the backstage yarn plugin. Addresses issue #28139 Signed-off-by: Cory Steers --- docs/getting-started/keeping-backstage-updated.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/getting-started/keeping-backstage-updated.md b/docs/getting-started/keeping-backstage-updated.md index a811a33383..bf0b569c29 100644 --- a/docs/getting-started/keeping-backstage-updated.md +++ b/docs/getting-started/keeping-backstage-updated.md @@ -30,6 +30,7 @@ yarn backstage-cli versions:bump The reason for bumping all `@backstage` packages at once is to maintain the dependencies that they have between each other. + :::tip To make the version bump process even easier and more streamlined we highly recommend using the [Backstage yarn plugin](#managing-package-versions-with-the-backstage-yarn-plugin) @@ -142,10 +143,17 @@ down the number of duplicate packages. The Backstage CLI uses [global-agent](https://www.npmjs.com/package/global-agent) to configure HTTP/HTTPS proxy settings using environment variables. This allows you to route the CLI’s network traffic through a proxy server, which can be useful in environments with restricted internet access. +Additionally, yarn needs a proxy too (sometimes), when in environments with restricted internet access. It uses different settings than the global-agent module. If you decide to use the backstage yarn plugin [mentioned above](#plugin), you will need to set additional proxy values. +If you will always need proxy settings in all environments and situations, you can add `httpProxy` and `httpsProxy` values to [the yarnrc.yml file](https://yarnpkg.com/configuration/yarnrc). If some environments need it (say a developer workstation) but other environments do not (perhaps a CI build server running on AWS), then you may not want to update the yarnrc.yml file but just set environment variables `YARN_HTTP_PROXY` and `YARN_HTTPS_PROXY` in the environments/situations where you need to proxy. + +**If you plan to use the backstage yarn plugin, you will need these extra yarn proxy settings to both install the plugin and run the `versions:bump` command**. If you do not plan to use the backstage yarn plugin, it seems like the global agent proxy settings alone are sufficient. + ### Example Configuration ```bash export GLOBAL_AGENT_HTTP_PROXY=http://proxy.company.com:8080 export GLOBAL_AGENT_HTTPS_PROXY=https://secure-proxy.company.com:8080 export GLOBAL_AGENT_NO_PROXY=localhost,internal.company.com +export YARN_HTTP_PROXY=http://proxy.company.com:8080 # optional +export YARN_HTTPS_PROXY=https://secure-proxy.company.com:8080 # optional ``` From e1d50e2ce5f210862ad4e0240fe5b19316925e98 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 24 Dec 2024 12:35:52 +0100 Subject: [PATCH 050/420] cli: add module support for rollup build Signed-off-by: Patrik Oldsberg --- packages/cli-node/report.api.md | 2 + .../cli-node/src/monorepo/PackageGraph.ts | 2 + packages/cli/src/lib/builder/config.ts | 99 +++++++++++++++++-- packages/cli/src/lib/builder/packager.ts | 2 +- .../tests/transforms/__fixtures__/.gitignore | 1 + .../__fixtures__/pkg-default/package.json | 5 +- .../__fixtures__/pkg-module/main.ts | 4 +- .../src/tests/transforms/transforms.test.ts | 78 ++++++++++++++- 8 files changed, 180 insertions(+), 13 deletions(-) diff --git a/packages/cli-node/report.api.md b/packages/cli-node/report.api.md index 7c1a3bc744..bec25ea251 100644 --- a/packages/cli-node/report.api.md +++ b/packages/cli-node/report.api.md @@ -72,6 +72,8 @@ export interface BackstagePackageJson { [key: string]: string; }; // (undocumented) + type?: 'module' | 'commonjs'; + // (undocumented) types?: string; // (undocumented) typesVersions?: Record>; diff --git a/packages/cli-node/src/monorepo/PackageGraph.ts b/packages/cli-node/src/monorepo/PackageGraph.ts index 721aa79e50..1da896a057 100644 --- a/packages/cli-node/src/monorepo/PackageGraph.ts +++ b/packages/cli-node/src/monorepo/PackageGraph.ts @@ -43,6 +43,8 @@ export interface BackstagePackageJson { // that the package bundles all of its dependencies in its build output. bundled?: boolean; + type?: 'module' | 'commonjs'; + backstage?: { role?: PackageRole; moved?: string; diff --git a/packages/cli/src/lib/builder/config.ts b/packages/cli/src/lib/builder/config.ts index e419503649..c52efd6c2d 100644 --- a/packages/cli/src/lib/builder/config.ts +++ b/packages/cli/src/lib/builder/config.ts @@ -16,7 +16,11 @@ import chalk from 'chalk'; import fs from 'fs-extra'; -import { relative as relativePath, resolve as resolvePath } from 'path'; +import { + extname, + relative as relativePath, + resolve as resolvePath, +} from 'path'; import commonjs from '@rollup/plugin-commonjs'; import resolve from '@rollup/plugin-node-resolve'; import postcss from 'rollup-plugin-postcss'; @@ -29,6 +33,7 @@ import { RollupOptions, OutputOptions, WarningHandlerWithDefault, + OutputPlugin, } from 'rollup'; import { forwardFileImports } from './plugins'; @@ -40,6 +45,11 @@ import { readEntryPoints } from '../entryPoints'; const SCRIPT_EXTS = ['.js', '.jsx', '.ts', '.tsx']; +const MODULE_EXTS = ['.mjs', '.mts']; +const COMMONJS_EXTS = ['.cjs', '.cts']; +const MOD_EXT = '.mjs'; +const CJS_EXT = '.cjs'; + function isFileImport(source: string) { if (source.startsWith('.')) { return true; @@ -68,6 +78,39 @@ function buildInternalImportPattern(options: BuildOptions) { return new RegExp(`^(?:${names.join('|')})(?:$|/)`); } +// This Rollup output plugin enables support for mixed CommonJS and ESM output. +// It does it be filtering out the unwanted output files that don't match the +// input file format, allowing the rollup configuration to have overlapping +// output configurations for different formats. +function multiOutputFormat(): OutputPlugin { + return { + name: 'backstage-multi-output-format', + generateBundle(opts, bundle) { + const filter: (name: string) => boolean = + opts.format === 'cjs' + ? s => s.endsWith(MOD_EXT) + : s => !s.endsWith(MOD_EXT); + + // Delete any files that don't match the current output format + for (const name in bundle) { + if (filter(name)) { + delete bundle[name]; + delete bundle[`${name}.map`]; + } + } + }, + renderDynamicImport(opts) { + if (opts.format === 'cjs') { + return { + left: 'import(', + right: ')', + }; + } + return undefined; + }, + }; +} + export async function makeRollupConfigs( options: BuildOptions, ): Promise { @@ -120,18 +163,46 @@ export async function makeRollupConfigs( const rewriteNodeModules = (name: string) => name.replaceAll('node_modules', 'node_modules_dist'); + // For CommonJS we build both CommonJS and ESM output. Each of these outputs + // can output both .cjs and .mjs files. The files from each of these outputs + // will overlap, but we trim away files where the format doesn't match the + // file extensions. That way we are left with a combination of .cjs and .mjs + // files where the module format in the file matches the file extension. if (options.outputs.has(Output.cjs)) { - output.push({ + const defaultExt = targetPkg.type === 'module' ? MOD_EXT : CJS_EXT; + const outputOpts: OutputOptions = { dir: distDir, - entryFileNames: chunkInfo => - `${rewriteNodeModules(chunkInfo.name)}.cjs.js`, - chunkFileNames: `cjs/[name]-[hash].cjs.js`, - format: 'commonjs', - interop: 'compat', + entryFileNames(chunkInfo) { + const cleanName = rewriteNodeModules(chunkInfo.name); + + const inputId = chunkInfo.facadeModuleId; + if (!inputId) { + return cleanName + defaultExt; + } + + const inputExt = extname(inputId); + if (MODULE_EXTS.includes(inputExt)) { + return cleanName + MOD_EXT; + } + if (COMMONJS_EXTS.includes(inputExt)) { + return cleanName + CJS_EXT; + } + return cleanName + defaultExt; + }, sourcemap: true, preserveModules: true, preserveModulesRoot: `${targetDir}/src`, exports: 'named', + plugins: [multiOutputFormat()], + }; + + output.push({ + ...outputOpts, + format: 'cjs', + }); + output.push({ + ...outputOpts, + format: 'module', }); } if (options.outputs.has(Output.esm)) { @@ -160,7 +231,19 @@ export async function makeRollupConfigs( // All module imports are always marked as external external, plugins: [ - resolve({ mainFields }), + resolve({ + mainFields, + extensions: [ + '.ts', + '.js', + '.tsx', + '.jsx', + '.mts', + '.cts', + '.mjs', + '.cjs', + ], + }), commonjs({ include: /node_modules/, exclude: [/\/[^/]+\.(?:stories|test)\.[^/]+$/], diff --git a/packages/cli/src/lib/builder/packager.ts b/packages/cli/src/lib/builder/packager.ts index be9bc9a6ea..b709cb0408 100644 --- a/packages/cli/src/lib/builder/packager.ts +++ b/packages/cli/src/lib/builder/packager.ts @@ -107,7 +107,7 @@ export const buildPackage = async (options: BuildOptions) => { const rollupConfigs = await makeRollupConfigs(options); - await fs.remove(paths.resolveTarget('dist')); + await fs.remove(resolvePath(options.targetDir ?? paths.targetDir, 'dist')); const buildTasks = rollupConfigs.map(rollupBuild); diff --git a/packages/cli/src/tests/transforms/__fixtures__/.gitignore b/packages/cli/src/tests/transforms/__fixtures__/.gitignore index cf4bab9ddd..dd13a98e05 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/.gitignore +++ b/packages/cli/src/tests/transforms/__fixtures__/.gitignore @@ -1 +1,2 @@ !node_modules +dist diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/package.json b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/package.json index 9881bbf3a8..1bfa22188c 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/package.json +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/package.json @@ -1,3 +1,6 @@ { - "name": "pkg-default" + "name": "pkg-default", + "exports": { + ".": "./main.ts" + } } diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts index 7b40a259f6..4c6c8f0079 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts @@ -21,10 +21,10 @@ import * as depModule from 'dep-module'; import * as depDefault from 'dep-default'; import { value as namedA } from './a-named'; import { value as namedB } from './b-named'; -import cNamed from './c-named'; +import * as cNamed from './c-named'; import defaultA from './a-default'; import defaultB from './b-default'; -import cDefault from './c-default'; +import * as cDefault from './c-default'; const { default: defaultC } = cDefault; const { value: namedC } = cNamed; diff --git a/packages/cli/src/tests/transforms/transforms.test.ts b/packages/cli/src/tests/transforms/transforms.test.ts index ac67069b77..6f9c291bc7 100644 --- a/packages/cli/src/tests/transforms/transforms.test.ts +++ b/packages/cli/src/tests/transforms/transforms.test.ts @@ -16,6 +16,7 @@ import { execFileSync } from 'child_process'; import { resolve as resolvePath } from 'path'; +import { Output, buildPackage } from '../../lib/builder'; const exportValues = { all: { @@ -95,7 +96,12 @@ describe('node runtime module transforms', () => { dynCommonJs: expectedExports.commonJs, dynDefault: expectedExports.commonJs, dynModule: expectedExports.module, - dep: exportValues.all, + // TODO(Rugvip): Fix CommonJS import compat from modules + dep: { + ...exportValues.all, + defaultC: { default: 'c' }, + namedC: undefined, + }, dyn: exportValues.all, }); }); @@ -156,3 +162,73 @@ describe('Jest runtime module transforms', () => { }); }); }); + +describe('package build transforms', () => { + it('should build and load from commonjs format', async () => { + const pkgPath = resolvePath(__dirname, '__fixtures__/pkg-commonjs'); + + await buildPackage({ + targetDir: pkgPath, + outputs: new Set([Output.cjs]), + workspacePackages: [], + }); + const values = await import(resolvePath(pkgPath, 'dist/index.cjs')).then( + m => m.values, + ); + expect(values).toEqual({ + depCommonJs: expectedExports.commonJs, + depDefault: expectedExports.commonJs, + dynCommonJs: expectedExports.commonJs, + dynDefault: expectedExports.commonJs, + dynModule: expectedExports.module, + dep: exportValues.commonJs, + dyn: exportValues.all, + }); + }); + + it('should build and load from default format', async () => { + const pkgPath = resolvePath(__dirname, '__fixtures__/pkg-default'); + + await buildPackage({ + targetDir: pkgPath, + outputs: new Set([Output.cjs]), + workspacePackages: [], + }); + const values = await import(resolvePath(pkgPath, 'dist/index.cjs')).then( + m => m.values, + ); + expect(values).toEqual({ + depCommonJs: expectedExports.commonJs, + depDefault: expectedExports.commonJs, + dynCommonJs: expectedExports.commonJs, + dynDefault: expectedExports.commonJs, + dynModule: expectedExports.module, + dep: exportValues.commonJs, + dyn: exportValues.all, + }); + }); + + it('should build and load from module format', async () => { + const pkgPath = resolvePath(__dirname, '__fixtures__/pkg-module'); + + await buildPackage({ + targetDir: pkgPath, + outputs: new Set([Output.cjs]), + workspacePackages: [], + }); + const values = await import(resolvePath(pkgPath, 'dist/index.mjs')).then( + m => m.values, + ); + expect(values).toEqual({ + depCommonJs: expectedExports.commonJs, + depDefault: expectedExports.commonJs, + depModule: expectedExports.module, + dynCommonJs: expectedExports.commonJs, + dynDefault: expectedExports.commonJs, + dynModule: expectedExports.module, + // TODO(Rugvip): Fix CommonJS import compat from modules + dep: { ...exportValues.all, defaultC: { default: 'c' } }, + dyn: exportValues.all, + }); + }); +}); From d36f7fd6f865698801510f48b82a6d41b463f98a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 24 Dec 2024 12:41:26 +0100 Subject: [PATCH 051/420] cli: use commonjs compat mode for .cts files Signed-off-by: Patrik Oldsberg --- packages/cli/config/nodeTransformHooks.mjs | 8 +++++--- .../tests/transforms/__fixtures__/pkg-module/main.ts | 5 ++--- packages/cli/src/tests/transforms/transforms.test.ts | 10 ++-------- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/packages/cli/config/nodeTransformHooks.mjs b/packages/cli/config/nodeTransformHooks.mjs index 4a897ca6c7..c19738c1ed 100644 --- a/packages/cli/config/nodeTransformHooks.mjs +++ b/packages/cli/config/nodeTransformHooks.mjs @@ -246,11 +246,13 @@ export async function load(url, context, nextLoad) { // all that well though, and can lead to module loading issues in many cases, // especially for older code. - // This `if` block opts-out of using CommonJS compatibility mode, and instead - // leaves it to our existing loader to transform CommonJS. + // This `if` block opts-out of using CommonJS compatibility mode by default, + // and instead leaves it to our existing loader to transform CommonJS. We do + // however use compatibility mode for the more explicit .cts file extension, + // allows for a way to opt-in to the new behavior. // // TODO(Rugvip): Once the synchronous hooks API is available for us to use, we might be able to adopt that instead - if (format === 'commonjs') { + if (format === 'commonjs' && ext !== '.cts') { return nextLoad(url, { ...context, format }); } diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts index 4c6c8f0079..56ee59fdd7 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts @@ -21,13 +21,12 @@ import * as depModule from 'dep-module'; import * as depDefault from 'dep-default'; import { value as namedA } from './a-named'; import { value as namedB } from './b-named'; -import * as cNamed from './c-named'; +import { value as namedC } from './c-named'; import defaultA from './a-default'; import defaultB from './b-default'; -import * as cDefault from './c-default'; +import cDefault from './c-default'; const { default: defaultC } = cDefault; -const { value: namedC } = cNamed; async function resolveAll(obj): Promise { const val = await obj; diff --git a/packages/cli/src/tests/transforms/transforms.test.ts b/packages/cli/src/tests/transforms/transforms.test.ts index 6f9c291bc7..c15bd063d4 100644 --- a/packages/cli/src/tests/transforms/transforms.test.ts +++ b/packages/cli/src/tests/transforms/transforms.test.ts @@ -96,12 +96,7 @@ describe('node runtime module transforms', () => { dynCommonJs: expectedExports.commonJs, dynDefault: expectedExports.commonJs, dynModule: expectedExports.module, - // TODO(Rugvip): Fix CommonJS import compat from modules - dep: { - ...exportValues.all, - defaultC: { default: 'c' }, - namedC: undefined, - }, + dep: exportValues.all, dyn: exportValues.all, }); }); @@ -226,8 +221,7 @@ describe('package build transforms', () => { dynCommonJs: expectedExports.commonJs, dynDefault: expectedExports.commonJs, dynModule: expectedExports.module, - // TODO(Rugvip): Fix CommonJS import compat from modules - dep: { ...exportValues.all, defaultC: { default: 'c' } }, + dep: exportValues.all, dyn: exportValues.all, }); }); From e00a634225986d377fece3acdaf6365348a0d571 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 24 Dec 2024 13:14:39 +0100 Subject: [PATCH 052/420] cli: add type definitions for transform test fixtures Signed-off-by: Patrik Oldsberg --- .../node_modules/dep-commonjs/main.d.ts | 15 +++++++++++++++ .../node_modules/dep-commonjs/package.json | 5 +++++ .../node_modules/dep-default/main.d.ts | 15 +++++++++++++++ .../node_modules/dep-default/package.json | 5 +++++ .../node_modules/dep-module/main.d.ts | 15 +++++++++++++++ .../node_modules/dep-module/package.json | 5 +++++ 6 files changed, 60 insertions(+) create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/main.d.ts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/main.d.ts create mode 100644 packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/main.d.ts diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/main.d.ts b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/main.d.ts new file mode 100644 index 0000000000..bdc1e24a02 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/main.d.ts @@ -0,0 +1,15 @@ +export const namedA: string +export const namedB: string +export const namedC: string +export const defaultA: string +export const defaultB: string +export const defaultC: string + +export namespace dyn { + export const namedA: Promise + export const namedB: Promise + export const namedC: Promise + export const defaultA: Promise + export const defaultB: Promise + export const defaultC: Promise +} diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/package.json b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/package.json index 348dc7e258..c5b04671bb 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/package.json +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-commonjs/package.json @@ -3,5 +3,10 @@ "type": "commonjs", "exports": { ".": "./main.js" + }, + "typesVersions": { + "*": { + "*": [ "main.d.ts" ] + } } } diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/main.d.ts b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/main.d.ts new file mode 100644 index 0000000000..bdc1e24a02 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/main.d.ts @@ -0,0 +1,15 @@ +export const namedA: string +export const namedB: string +export const namedC: string +export const defaultA: string +export const defaultB: string +export const defaultC: string + +export namespace dyn { + export const namedA: Promise + export const namedB: Promise + export const namedC: Promise + export const defaultA: Promise + export const defaultB: Promise + export const defaultC: Promise +} diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/package.json b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/package.json index 1543cf8010..8deb7ce3f4 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/package.json +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-default/package.json @@ -2,5 +2,10 @@ "name": "dep-default", "exports": { ".": "./main.js" + }, + "typesVersions": { + "*": { + "*": [ "main.d.ts" ] + } } } diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/main.d.ts b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/main.d.ts new file mode 100644 index 0000000000..bdc1e24a02 --- /dev/null +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/main.d.ts @@ -0,0 +1,15 @@ +export const namedA: string +export const namedB: string +export const namedC: string +export const defaultA: string +export const defaultB: string +export const defaultC: string + +export namespace dyn { + export const namedA: Promise + export const namedB: Promise + export const namedC: Promise + export const defaultA: Promise + export const defaultB: Promise + export const defaultC: Promise +} diff --git a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/package.json b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/package.json index b28b4b6562..29de5c862a 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/package.json +++ b/packages/cli/src/tests/transforms/__fixtures__/node_modules/dep-module/package.json @@ -3,5 +3,10 @@ "type": "module", "exports": { ".": "./main.js" + }, + "typesVersions": { + "*": { + "*": [ "main.d.ts" ] + } } } From d7b0ef9515977bfbb5e9369a6943d7afe10a475b Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 24 Dec 2024 13:42:00 +0100 Subject: [PATCH 053/420] cli: remove support for implicit module extensions + allow .?ts Signed-off-by: Patrik Oldsberg --- packages/cli/config/nodeTransformHooks.mjs | 4 ++-- packages/cli/config/tsconfig.json | 1 + packages/cli/src/lib/builder/config.ts | 11 +---------- .../transforms/__fixtures__/pkg-commonjs/main.ts | 16 ++++++++-------- .../transforms/__fixtures__/pkg-default/main.ts | 16 ++++++++-------- .../transforms/__fixtures__/pkg-module/main.ts | 16 ++++++++-------- 6 files changed, 28 insertions(+), 36 deletions(-) diff --git a/packages/cli/config/nodeTransformHooks.mjs b/packages/cli/config/nodeTransformHooks.mjs index c19738c1ed..b7f3248546 100644 --- a/packages/cli/config/nodeTransformHooks.mjs +++ b/packages/cli/config/nodeTransformHooks.mjs @@ -27,7 +27,7 @@ import { existsSync } from 'fs'; const DEFAULT_MODULE_FORMAT = 'commonjs'; // Source file extensions to look for when using bundle resolution strategy -const EXTS = ['.ts', '.js', '.mts', '.cts', '.mjs', '.cjs']; +const SRC_EXTS = ['.ts', '.js']; const TS_EXTS = ['.ts', '.mts', '.cts']; const moduleTypeTable = { '.mjs': 'module', @@ -177,7 +177,7 @@ async function findPackageJSON(startPath) { /** @type {import('module').ResolveHook} */ async function resolveWithoutExt(specifier, context, nextResolve) { - for (const tryExt of EXTS) { + for (const tryExt of SRC_EXTS) { try { const resolved = await nextResolve(specifier + tryExt, { ...context, diff --git a/packages/cli/config/tsconfig.json b/packages/cli/config/tsconfig.json index aa25e59451..ac5e62b52e 100644 --- a/packages/cli/config/tsconfig.json +++ b/packages/cli/config/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "allowImportingTsExtensions": true, "allowJs": true, "declaration": true, "declarationMap": false, diff --git a/packages/cli/src/lib/builder/config.ts b/packages/cli/src/lib/builder/config.ts index c52efd6c2d..8bde17d7fe 100644 --- a/packages/cli/src/lib/builder/config.ts +++ b/packages/cli/src/lib/builder/config.ts @@ -233,16 +233,7 @@ export async function makeRollupConfigs( plugins: [ resolve({ mainFields, - extensions: [ - '.ts', - '.js', - '.tsx', - '.jsx', - '.mts', - '.cts', - '.mjs', - '.cjs', - ], + extensions: SCRIPT_EXTS, }), commonjs({ include: /node_modules/, diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/main.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/main.ts index c0494e86b6..5703eb1184 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/main.ts +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/main.ts @@ -20,11 +20,11 @@ import * as depCommonJs from 'dep-commonjs'; // import * as depModule from 'dep-module'; import * as depDefault from 'dep-default'; import { value as namedA } from './a-named'; -// import { value as namedB } from './b-named'; -import { value as namedC } from './c-named'; +// import { value as namedB } from './b-named.mts'; +import { value as namedC } from './c-named.cts'; import { default as defaultA } from './a-default'; -// import { default as defaultB } from './b-default'; -import { default as defaultC } from './c-default'; +// import { default as defaultB } from './b-default.mts'; +import { default as defaultC } from './c-default.cts'; async function resolveAll(obj): Promise { const val = await obj; @@ -61,10 +61,10 @@ export const values = resolveAll({ }, dyn: { namedA: import('./a-named').then(m => m.default.value), - namedB: import('./b-named').then(m => m.value), - namedC: import('./c-named').then(m => m.default.value), + namedB: import('./b-named.mts').then(m => m.value), + namedC: import('./c-named.cts').then(m => m.default.value), defaultA: import('./a-default').then(m => m.default.default), - defaultB: import('./b-default').then(m => m.default), - defaultC: import('./c-default').then(m => m.default.default), + defaultB: import('./b-default.mts').then(m => m.default), + defaultC: import('./c-default.cts').then(m => m.default.default), }, }); diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/main.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/main.ts index c0494e86b6..5703eb1184 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/main.ts +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/main.ts @@ -20,11 +20,11 @@ import * as depCommonJs from 'dep-commonjs'; // import * as depModule from 'dep-module'; import * as depDefault from 'dep-default'; import { value as namedA } from './a-named'; -// import { value as namedB } from './b-named'; -import { value as namedC } from './c-named'; +// import { value as namedB } from './b-named.mts'; +import { value as namedC } from './c-named.cts'; import { default as defaultA } from './a-default'; -// import { default as defaultB } from './b-default'; -import { default as defaultC } from './c-default'; +// import { default as defaultB } from './b-default.mts'; +import { default as defaultC } from './c-default.cts'; async function resolveAll(obj): Promise { const val = await obj; @@ -61,10 +61,10 @@ export const values = resolveAll({ }, dyn: { namedA: import('./a-named').then(m => m.default.value), - namedB: import('./b-named').then(m => m.value), - namedC: import('./c-named').then(m => m.default.value), + namedB: import('./b-named.mts').then(m => m.value), + namedC: import('./c-named.cts').then(m => m.default.value), defaultA: import('./a-default').then(m => m.default.default), - defaultB: import('./b-default').then(m => m.default), - defaultC: import('./c-default').then(m => m.default.default), + defaultB: import('./b-default.mts').then(m => m.default), + defaultC: import('./c-default.cts').then(m => m.default.default), }, }); diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts index 56ee59fdd7..bc8c9f3fdd 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts @@ -20,11 +20,11 @@ import * as depCommonJs from 'dep-commonjs'; import * as depModule from 'dep-module'; import * as depDefault from 'dep-default'; import { value as namedA } from './a-named'; -import { value as namedB } from './b-named'; -import { value as namedC } from './c-named'; +import { value as namedB } from './b-named.mts'; +import { value as namedC } from './c-named.cts'; import defaultA from './a-default'; -import defaultB from './b-default'; -import cDefault from './c-default'; +import defaultB from './b-default.mts'; +import cDefault from './c-default.cts'; const { default: defaultC } = cDefault; @@ -63,10 +63,10 @@ export const values = resolveAll({ }, dyn: { namedA: import('./a-named').then(m => m.value), - namedB: import('./b-named').then(m => m.value), - namedC: import('./c-named').then(m => m.default.value), + namedB: import('./b-named.mts').then(m => m.value), + namedC: import('./c-named.cts').then(m => m.default.value), defaultA: import('./a-default').then(m => m.default), - defaultB: import('./b-default').then(m => m.default), - defaultC: import('./c-default').then(m => m.default.default), + defaultB: import('./b-default.mts').then(m => m.default), + defaultC: import('./c-default.cts').then(m => m.default.default), }, }); From 5676658ca2faefd77174b711586b39f644cd0bf9 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 24 Dec 2024 13:51:44 +0100 Subject: [PATCH 054/420] cli: enable type checking in transform tests Signed-off-by: Patrik Oldsberg --- .../tests/transforms/__fixtures__/pkg-commonjs/main.ts | 9 +++++---- .../tests/transforms/__fixtures__/pkg-default/main.ts | 9 +++++---- .../src/tests/transforms/__fixtures__/pkg-module/main.ts | 8 ++++---- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/main.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/main.ts index 5703eb1184..d6bdfb30d1 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/main.ts +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/main.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -// @ts-nocheck - import * as depCommonJs from 'dep-commonjs'; // import * as depModule from 'dep-module'; import * as depDefault from 'dep-default'; @@ -26,7 +24,7 @@ import { default as defaultA } from './a-default'; // import { default as defaultB } from './b-default.mts'; import { default as defaultC } from './c-default.cts'; -async function resolveAll(obj): Promise { +async function resolveAll(obj: object): Promise { const val = await obj; if (typeof val !== 'object' || val === null) { return val; @@ -60,11 +58,14 @@ export const values = resolveAll({ defaultC, }, dyn: { + // @ts-expect-error Default exports from CommonJS are not well supported namedA: import('./a-named').then(m => m.default.value), namedB: import('./b-named.mts').then(m => m.value), - namedC: import('./c-named.cts').then(m => m.default.value), + namedC: import('./c-named.cts').then(m => m.value), + // @ts-expect-error Default exports from CommonJS are not well supported defaultA: import('./a-default').then(m => m.default.default), defaultB: import('./b-default.mts').then(m => m.default), + // @ts-expect-error Default exports from CommonJS are not well supported defaultC: import('./c-default.cts').then(m => m.default.default), }, }); diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/main.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/main.ts index 5703eb1184..d6bdfb30d1 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/main.ts +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/main.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -// @ts-nocheck - import * as depCommonJs from 'dep-commonjs'; // import * as depModule from 'dep-module'; import * as depDefault from 'dep-default'; @@ -26,7 +24,7 @@ import { default as defaultA } from './a-default'; // import { default as defaultB } from './b-default.mts'; import { default as defaultC } from './c-default.cts'; -async function resolveAll(obj): Promise { +async function resolveAll(obj: object): Promise { const val = await obj; if (typeof val !== 'object' || val === null) { return val; @@ -60,11 +58,14 @@ export const values = resolveAll({ defaultC, }, dyn: { + // @ts-expect-error Default exports from CommonJS are not well supported namedA: import('./a-named').then(m => m.default.value), namedB: import('./b-named.mts').then(m => m.value), - namedC: import('./c-named.cts').then(m => m.default.value), + namedC: import('./c-named.cts').then(m => m.value), + // @ts-expect-error Default exports from CommonJS are not well supported defaultA: import('./a-default').then(m => m.default.default), defaultB: import('./b-default.mts').then(m => m.default), + // @ts-expect-error Default exports from CommonJS are not well supported defaultC: import('./c-default.cts').then(m => m.default.default), }, }); diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts index bc8c9f3fdd..a4133495c7 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/main.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -// @ts-nocheck - import * as depCommonJs from 'dep-commonjs'; import * as depModule from 'dep-module'; import * as depDefault from 'dep-default'; @@ -26,9 +24,10 @@ import defaultA from './a-default'; import defaultB from './b-default.mts'; import cDefault from './c-default.cts'; +// @ts-expect-error Default exports from CommonJS are not well supported const { default: defaultC } = cDefault; -async function resolveAll(obj): Promise { +async function resolveAll(obj: object): Promise { const val = await obj; if (typeof val !== 'object' || val === null) { return val; @@ -64,9 +63,10 @@ export const values = resolveAll({ dyn: { namedA: import('./a-named').then(m => m.value), namedB: import('./b-named.mts').then(m => m.value), - namedC: import('./c-named.cts').then(m => m.default.value), + namedC: import('./c-named.cts').then(m => m.value), defaultA: import('./a-default').then(m => m.default), defaultB: import('./b-default.mts').then(m => m.default), + // @ts-expect-error Default exports from CommonJS are not well supported defaultC: import('./c-default.cts').then(m => m.default.default), }, }); From ce74d76262d487e8b75719f017957b12a941061f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 25 Dec 2024 11:05:27 +0100 Subject: [PATCH 055/420] cli: better runtime support for ESM dependencies in tests Signed-off-by: Patrik Oldsberg --- packages/cli/config/jestCachingModuleLoader.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/cli/config/jestCachingModuleLoader.js b/packages/cli/config/jestCachingModuleLoader.js index 95c6212123..3b7740a4f7 100644 --- a/packages/cli/config/jestCachingModuleLoader.js +++ b/packages/cli/config/jestCachingModuleLoader.js @@ -33,4 +33,22 @@ module.exports = class CachingJestRuntime extends JestRuntime { } return script; } + + // Notes(Rugvip): As far as I can tell this is the best we can currently do + // for runtime ESM support in Jest. What the below logic effectively does is + // to only allow packages to be loaded as ESM if all imports of that package + // are done in an ESM compatible way, as in either from ESM code or with a + // dynamic import. + cjsModules = new Set(); + _resolveCjsModule(...args) { + const path = super._resolveCjsModule(...args); + this.cjsModules.add(path); + return path; + } + unstable_shouldLoadAsEsm(path, ...restArgs) { + if (this.cjsModules.has(path)) { + return false; + } + return super.unstable_shouldLoadAsEsm(path, ...restArgs); + } }; From be9a1f8dc72d16b1845f139f3a40e6a38649ac8f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 26 Dec 2024 11:31:03 +0100 Subject: [PATCH 056/420] cli: simplify Jest ESM support Signed-off-by: Patrik Oldsberg --- .../cli/config/jestCachingModuleLoader.js | 20 ++++++++----------- packages/cli/config/jestSwcTransform.js | 9 +++++++-- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/packages/cli/config/jestCachingModuleLoader.js b/packages/cli/config/jestCachingModuleLoader.js index 3b7740a4f7..dd22f25f6f 100644 --- a/packages/cli/config/jestCachingModuleLoader.js +++ b/packages/cli/config/jestCachingModuleLoader.js @@ -19,6 +19,11 @@ const { default: JestRuntime } = require('jest-runtime'); const scriptTransformCache = new Map(); module.exports = class CachingJestRuntime extends JestRuntime { + constructor(config, ...restAgs) { + super(config, ...restAgs); + this.allowLoadAsEsm = config.extensionsToTreatAsEsm.includes('.mts'); + } + // This may or may not be a good idea. Theoretically I don't know why this would impact // test correctness and flakiness, but it seems like it may introduce flakiness and strange failures. // It does seem to speed up test execution by a fair amount though. @@ -34,19 +39,10 @@ module.exports = class CachingJestRuntime extends JestRuntime { return script; } - // Notes(Rugvip): As far as I can tell this is the best we can currently do - // for runtime ESM support in Jest. What the below logic effectively does is - // to only allow packages to be loaded as ESM if all imports of that package - // are done in an ESM compatible way, as in either from ESM code or with a - // dynamic import. - cjsModules = new Set(); - _resolveCjsModule(...args) { - const path = super._resolveCjsModule(...args); - this.cjsModules.add(path); - return path; - } + // Unfortunately we need to use this unstable API to make sure that .js files + // are only loaded as modules where ESM is supported, i.e. Node.js packages. unstable_shouldLoadAsEsm(path, ...restArgs) { - if (this.cjsModules.has(path)) { + if (!this.allowLoadAsEsm) { return false; } return super.unstable_shouldLoadAsEsm(path, ...restArgs); diff --git a/packages/cli/config/jestSwcTransform.js b/packages/cli/config/jestSwcTransform.js index 203bb73d80..a77683b939 100644 --- a/packages/cli/config/jestSwcTransform.js +++ b/packages/cli/config/jestSwcTransform.js @@ -18,13 +18,18 @@ const { createTransformer: createSwcTransformer } = require('@swc/jest'); const ESM_REGEX = /\b(?:import|export)\b/; function createTransformer(config) { - const useModules = Boolean(config?.module); const swcTransformer = createSwcTransformer({ inputSourceMap: false, ...config, }); const process = (source, filePath, jestOptions) => { - if (filePath.endsWith('.js') && (useModules || !ESM_REGEX.test(source))) { + // Skip transformation of .js files without ESM syntax, we never transform from CJS to ESM + if (filePath.endsWith('.js') && !ESM_REGEX.test(source)) { + return { code: source }; + } + + // Skip transformation of .mjs files, they should only be used if ESM support is available + if (filePath.endsWith('.mjs')) { return { code: source }; } From f866b865212faee227d2c38df536d94ca2dd5304 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 26 Dec 2024 11:52:09 +0100 Subject: [PATCH 057/420] switch to explicit require for lazy-loading dependencies in node packages Signed-off-by: Patrik Oldsberg --- .changeset/curly-humans-prove.md | 7 +++++++ .../src/entrypoints/database/connectors/postgres.ts | 2 +- packages/backend-test-utils/src/cache/memcache.ts | 3 ++- packages/backend-test-utils/src/cache/redis.ts | 3 ++- packages/backend-test-utils/src/database/mysql.ts | 3 ++- packages/backend-test-utils/src/database/postgres.ts | 3 ++- packages/config-loader/src/schema/collect.ts | 5 ++--- 7 files changed, 18 insertions(+), 8 deletions(-) create mode 100644 .changeset/curly-humans-prove.md diff --git a/.changeset/curly-humans-prove.md b/.changeset/curly-humans-prove.md new file mode 100644 index 0000000000..4e0ca9c90e --- /dev/null +++ b/.changeset/curly-humans-prove.md @@ -0,0 +1,7 @@ +--- +'@backstage/backend-test-utils': patch +'@backstage/backend-defaults': patch +'@backstage/config-loader': patch +--- + +Internal refactor to use explicit `require` for lazy-loading dependency. diff --git a/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts b/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts index e7e973ae4b..3583049a7d 100644 --- a/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts +++ b/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts @@ -103,7 +103,7 @@ export async function buildPgDatabaseConfig( Connector: CloudSqlConnector, IpAddressTypes, AuthTypes, - } = await import('@google-cloud/cloud-sql-connector'); + } = require('@google-cloud/cloud-sql-connector') as typeof import('@google-cloud/cloud-sql-connector'); const connector = new CloudSqlConnector(); const clientOpts = await connector.getOptions({ instanceConnectionName: config.connection.instance, diff --git a/packages/backend-test-utils/src/cache/memcache.ts b/packages/backend-test-utils/src/cache/memcache.ts index b7ac07cb13..e985ee1c7d 100644 --- a/packages/backend-test-utils/src/cache/memcache.ts +++ b/packages/backend-test-utils/src/cache/memcache.ts @@ -59,7 +59,8 @@ export async function startMemcachedContainer( image: string, ): Promise { // Lazy-load to avoid side-effect of importing testcontainers - const { GenericContainer } = await import('testcontainers'); + const { GenericContainer } = + require('testcontainers') as typeof import('testcontainers'); const container = await new GenericContainer(image) .withExposedPorts(11211) diff --git a/packages/backend-test-utils/src/cache/redis.ts b/packages/backend-test-utils/src/cache/redis.ts index 6185e4d076..cd6e5c2893 100644 --- a/packages/backend-test-utils/src/cache/redis.ts +++ b/packages/backend-test-utils/src/cache/redis.ts @@ -57,7 +57,8 @@ export async function connectToExternalRedis( export async function startRedisContainer(image: string): Promise { // Lazy-load to avoid side-effect of importing testcontainers - const { GenericContainer } = await import('testcontainers'); + const { GenericContainer } = + require('testcontainers') as typeof import('testcontainers'); const container = await new GenericContainer(image) .withExposedPorts(6379) diff --git a/packages/backend-test-utils/src/database/mysql.ts b/packages/backend-test-utils/src/database/mysql.ts index b27edb7f9d..a7c11d8452 100644 --- a/packages/backend-test-utils/src/database/mysql.ts +++ b/packages/backend-test-utils/src/database/mysql.ts @@ -72,7 +72,8 @@ export async function startMysqlContainer(image: string): Promise<{ const password = uuid(); // Lazy-load to avoid side-effect of importing testcontainers - const { GenericContainer } = await import('testcontainers'); + const { GenericContainer } = + require('testcontainers') as typeof import('testcontainers'); const container = await new GenericContainer(image) .withExposedPorts(3306) diff --git a/packages/backend-test-utils/src/database/postgres.ts b/packages/backend-test-utils/src/database/postgres.ts index c8a946d121..e6122b9ab7 100644 --- a/packages/backend-test-utils/src/database/postgres.ts +++ b/packages/backend-test-utils/src/database/postgres.ts @@ -72,7 +72,8 @@ export async function startPostgresContainer(image: string): Promise<{ const password = uuid(); // Lazy-load to avoid side-effect of importing testcontainers - const { GenericContainer } = await import('testcontainers'); + const { GenericContainer } = + require('testcontainers') as typeof import('testcontainers'); const container = await new GenericContainer(image) .withExposedPorts(5432) diff --git a/packages/config-loader/src/schema/collect.ts b/packages/config-loader/src/schema/collect.ts index 7120c12d90..1d134b2c15 100644 --- a/packages/config-loader/src/schema/collect.ts +++ b/packages/config-loader/src/schema/collect.ts @@ -164,9 +164,8 @@ async function compileTsSchemas(paths: string[]) { // Lazy loaded, because this brings up all of TypeScript and we don't // want that eagerly loaded in tests - const { getProgramFromFiles, buildGenerator } = await import( - 'typescript-json-schema' - ); + const { getProgramFromFiles, buildGenerator } = + require('typescript-json-schema') as typeof import('typescript-json-schema'); const program = getProgramFromFiles(paths, { incremental: false, From fb051f274eb2736d1e6bc5728b22e7ff4efc3864 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 26 Dec 2024 12:12:04 +0100 Subject: [PATCH 058/420] backend-test-utils: sync feature compat unwrapping Signed-off-by: Patrik Oldsberg --- .changeset/dry-horses-report.md | 5 +++++ .../src/next/wiring/TestBackend.ts | 13 +++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 .changeset/dry-horses-report.md diff --git a/.changeset/dry-horses-report.md b/.changeset/dry-horses-report.md new file mode 100644 index 0000000000..b7a0b4cd46 --- /dev/null +++ b/.changeset/dry-horses-report.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-test-utils': patch +--- + +Sync feature installation compatibility logic with `@backstage/backend-app-api`. diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.ts b/packages/backend-test-utils/src/next/wiring/TestBackend.ts index 4471f6ed24..36e7073e4d 100644 --- a/packages/backend-test-utils/src/next/wiring/TestBackend.ts +++ b/packages/backend-test-utils/src/next/wiring/TestBackend.ts @@ -209,10 +209,19 @@ function isPromise(value: unknown | Promise): value is Promise { ); } +// Same as in the backend-app-api, handles double defaults from dynamic imports function unwrapFeature( - feature: BackendFeature | (() => BackendFeature), + feature: BackendFeature | { default: BackendFeature }, ): BackendFeature { - return typeof feature === 'function' ? feature() : feature; + if ('$$type' in feature) { + return feature; + } + + if ('default' in feature) { + return feature.default; + } + + return feature; } const backendInstancesToCleanUp = new Array(); From 96c20cd9cf6db6b5f0cf1d3c2611595e283d8466 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 26 Dec 2024 12:45:45 +0100 Subject: [PATCH 059/420] backend-dynamic-feature-service: wait for changes to be tracked Signed-off-by: Patrik Oldsberg --- .changeset/twelve-eyes-stare.md | 5 +++++ .../src/manager/plugin-manager.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/twelve-eyes-stare.md diff --git a/.changeset/twelve-eyes-stare.md b/.changeset/twelve-eyes-stare.md new file mode 100644 index 0000000000..bd2db50c9b --- /dev/null +++ b/.changeset/twelve-eyes-stare.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-dynamic-feature-service': patch +--- + +Make sure changes are successfully tracked before starting up scanner. diff --git a/packages/backend-dynamic-feature-service/src/manager/plugin-manager.ts b/packages/backend-dynamic-feature-service/src/manager/plugin-manager.ts index c64fca053c..398e9fb14c 100644 --- a/packages/backend-dynamic-feature-service/src/manager/plugin-manager.ts +++ b/packages/backend-dynamic-feature-service/src/manager/plugin-manager.ts @@ -68,7 +68,7 @@ export class DynamicPluginManager implements DynamicPluginProvider { preferAlpha: options.preferAlpha, }); const scannedPlugins = (await scanner.scanRoot()).packages; - scanner.trackChanges(); + await scanner.trackChanges(); const moduleLoader = options.moduleLoader || new CommonJSModuleLoader({ logger: options.logger }); From 45f09403e5a898b23869a1bfac98a69651796b85 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 26 Dec 2024 13:41:02 +0100 Subject: [PATCH 060/420] scaffolder-backend: fix loading of ESM-only module in test Signed-off-by: Patrik Oldsberg --- .../src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts index 3990ac14b6..5a931f2da0 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts @@ -38,7 +38,6 @@ import { mockCredentials, mockServices, } from '@backstage/backend-test-utils'; -import stripAnsi from 'strip-ansi'; import { loggerToWinstonLogger } from '@backstage/backend-common'; import { LoggerService } from '@backstage/backend-plugin-api'; @@ -48,6 +47,7 @@ describe('NunjucksWorkflowRunner', () => { let runner: NunjucksWorkflowRunner; let fakeActionHandler: jest.Mock; let fakeTaskLog: jest.Mock; + let stripAnsi: typeof import('strip-ansi').default; const mockDir = createMockDirectory(); @@ -92,9 +92,12 @@ describe('NunjucksWorkflowRunner', () => { ); } - beforeEach(() => { + beforeEach(async () => { mockDir.clear(); + // This one is ESM-only + stripAnsi = await import('strip-ansi').then(m => m.default); + jest.resetAllMocks(); logger = mockServices.logger.mock(); actionRegistry = new TemplateActionRegistry(); From ecea2074576c8212bccf530ab46d868d0cd892e5 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 26 Dec 2024 18:16:38 +0100 Subject: [PATCH 061/420] cli: also verify print output in build transform tests Signed-off-by: Patrik Oldsberg --- .../__fixtures__/pkg-commonjs/package.json | 3 +- .../__fixtures__/pkg-default/package.json | 3 +- .../__fixtures__/pkg-module/package.json | 3 +- .../src/tests/transforms/transforms.test.ts | 31 +++++++++++++++++++ 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/package.json b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/package.json index c622a8ba4d..b39408ee58 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/package.json +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-commonjs/package.json @@ -2,6 +2,7 @@ "name": "pkg-commonjs", "type": "commonjs", "exports": { - ".": "./main.ts" + ".": "./main.ts", + "./print": "./print.ts" } } diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/package.json b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/package.json index 1bfa22188c..5b0b075a3c 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/pkg-default/package.json +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-default/package.json @@ -1,6 +1,7 @@ { "name": "pkg-default", "exports": { - ".": "./main.ts" + ".": "./main.ts", + "./print": "./print.ts" } } diff --git a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/package.json b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/package.json index a04ea067a6..f7b7e1ab30 100644 --- a/packages/cli/src/tests/transforms/__fixtures__/pkg-module/package.json +++ b/packages/cli/src/tests/transforms/__fixtures__/pkg-module/package.json @@ -2,6 +2,7 @@ "name": "pkg-module", "type": "module", "exports": { - ".": "./main.ts" + ".": "./main.ts", + "./print": "./print.ts" } } diff --git a/packages/cli/src/tests/transforms/transforms.test.ts b/packages/cli/src/tests/transforms/transforms.test.ts index c15bd063d4..7538ed3b89 100644 --- a/packages/cli/src/tests/transforms/transforms.test.ts +++ b/packages/cli/src/tests/transforms/transforms.test.ts @@ -179,6 +179,16 @@ describe('package build transforms', () => { dep: exportValues.commonJs, dyn: exportValues.all, }); + + expect(loadFixture('pkg-commonjs/dist/print.cjs')).toEqual({ + depCommonJs: expectedExports.commonJs, + depDefault: expectedExports.commonJs, + dynCommonJs: expectedExports.commonJs, + dynDefault: expectedExports.commonJs, + dynModule: expectedExports.module, + dep: exportValues.commonJs, + dyn: exportValues.all, + }); }); it('should build and load from default format', async () => { @@ -201,6 +211,16 @@ describe('package build transforms', () => { dep: exportValues.commonJs, dyn: exportValues.all, }); + + expect(loadFixture('pkg-default/dist/print.cjs')).toEqual({ + depCommonJs: expectedExports.commonJs, + depDefault: expectedExports.commonJs, + dynCommonJs: expectedExports.commonJs, + dynDefault: expectedExports.commonJs, + dynModule: expectedExports.module, + dep: exportValues.commonJs, + dyn: exportValues.all, + }); }); it('should build and load from module format', async () => { @@ -224,5 +244,16 @@ describe('package build transforms', () => { dep: exportValues.all, dyn: exportValues.all, }); + + expect(loadFixture('pkg-module/dist/print.mjs')).toEqual({ + depCommonJs: expectedExports.commonJs, + depDefault: expectedExports.commonJs, + depModule: expectedExports.module, + dynCommonJs: expectedExports.commonJs, + dynDefault: expectedExports.commonJs, + dynModule: expectedExports.module, + dep: exportValues.all, + dyn: exportValues.all, + }); }); }); From ac27fdb5b5246dd43b1e9e1ece54e115f4c6877c Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 26 Dec 2024 18:49:16 +0100 Subject: [PATCH 062/420] cli: revert default cjs build extension to .cjs.js Signed-off-by: Patrik Oldsberg --- packages/cli/src/lib/builder/config.ts | 3 ++- packages/cli/src/tests/transforms/transforms.test.ts | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/cli/src/lib/builder/config.ts b/packages/cli/src/lib/builder/config.ts index 8bde17d7fe..3126383308 100644 --- a/packages/cli/src/lib/builder/config.ts +++ b/packages/cli/src/lib/builder/config.ts @@ -49,6 +49,7 @@ const MODULE_EXTS = ['.mjs', '.mts']; const COMMONJS_EXTS = ['.cjs', '.cts']; const MOD_EXT = '.mjs'; const CJS_EXT = '.cjs'; +const CJS_JS_EXT = '.cjs.js'; function isFileImport(source: string) { if (source.startsWith('.')) { @@ -169,7 +170,7 @@ export async function makeRollupConfigs( // file extensions. That way we are left with a combination of .cjs and .mjs // files where the module format in the file matches the file extension. if (options.outputs.has(Output.cjs)) { - const defaultExt = targetPkg.type === 'module' ? MOD_EXT : CJS_EXT; + const defaultExt = targetPkg.type === 'module' ? MOD_EXT : CJS_JS_EXT; const outputOpts: OutputOptions = { dir: distDir, entryFileNames(chunkInfo) { diff --git a/packages/cli/src/tests/transforms/transforms.test.ts b/packages/cli/src/tests/transforms/transforms.test.ts index 7538ed3b89..52af9a47d3 100644 --- a/packages/cli/src/tests/transforms/transforms.test.ts +++ b/packages/cli/src/tests/transforms/transforms.test.ts @@ -167,7 +167,7 @@ describe('package build transforms', () => { outputs: new Set([Output.cjs]), workspacePackages: [], }); - const values = await import(resolvePath(pkgPath, 'dist/index.cjs')).then( + const values = await import(resolvePath(pkgPath, 'dist/index.cjs.js')).then( m => m.values, ); expect(values).toEqual({ @@ -180,7 +180,7 @@ describe('package build transforms', () => { dyn: exportValues.all, }); - expect(loadFixture('pkg-commonjs/dist/print.cjs')).toEqual({ + expect(loadFixture('pkg-commonjs/dist/print.cjs.js')).toEqual({ depCommonJs: expectedExports.commonJs, depDefault: expectedExports.commonJs, dynCommonJs: expectedExports.commonJs, @@ -199,7 +199,7 @@ describe('package build transforms', () => { outputs: new Set([Output.cjs]), workspacePackages: [], }); - const values = await import(resolvePath(pkgPath, 'dist/index.cjs')).then( + const values = await import(resolvePath(pkgPath, 'dist/index.cjs.js')).then( m => m.values, ); expect(values).toEqual({ @@ -212,7 +212,7 @@ describe('package build transforms', () => { dyn: exportValues.all, }); - expect(loadFixture('pkg-default/dist/print.cjs')).toEqual({ + expect(loadFixture('pkg-default/dist/print.cjs.js')).toEqual({ depCommonJs: expectedExports.commonJs, depDefault: expectedExports.commonJs, dynCommonJs: expectedExports.commonJs, From deee6d584dcd948c031c47f090a2794070969b42 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 26 Dec 2024 18:49:32 +0100 Subject: [PATCH 063/420] docs/tooling/cli: initial ESM docs Signed-off-by: Patrik Oldsberg --- docs/tooling/cli/02-build-system.md | 56 +++++++++++++++++------------ 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/docs/tooling/cli/02-build-system.md b/docs/tooling/cli/02-build-system.md index 468e848694..c9b2fec5ec 100644 --- a/docs/tooling/cli/02-build-system.md +++ b/docs/tooling/cli/02-build-system.md @@ -483,29 +483,39 @@ of the build system, including the bundling, tests, builds, and type checking. Loaders are always selected based on the file extension. The following is a list of all supported file extensions: -| Extension | Exports | Purpose | -| --------- | ------------- | ------------------ | -| `.ts` | Script Module | TypeScript | -| `.tsx` | Script Module | TypeScript and XML | -| `.js` | Script Module | JavaScript | -| `.jsx` | Script Module | JavaScript and XML | -| `.mjs` | Script Module | ECMAScript Module | -| `.cjs` | Script Module | CommonJS Module | -| `.json` | JSON Data | JSON Data | -| `.yml` | JSON Data | YAML Data | -| `.yaml` | JSON Data | YAML Data | -| `.css` | classes | Style sheet | -| `.eot` | URL Path | Font | -| `.ttf` | URL Path | Font | -| `.woff2` | URL Path | Font | -| `.woff` | URL Path | Font | -| `.bmp` | URL Path | Image | -| `.gif` | URL Path | Image | -| `.jpeg` | URL Path | Image | -| `.jpg` | URL Path | Image | -| `.png` | URL Path | Image | -| `.svg` | URL Path | Image | -| `.md` | URL Path | Markdown File | +| Extension | Exports | Purpose | +| --------- | ------------- | ---------------------------- | +| `.ts` | Script Module | TypeScript | +| `.tsx` | Script Module | TypeScript and XML | +| `.mts` | Script Module | ECMAScript Module TypeScript | +| `.cts` | Script Module | CommonJS TypeScript | +| `.js` | Script Module | JavaScript | +| `.jsx` | Script Module | JavaScript and XML | +| `.mjs` | Script Module | ECMAScript Module | +| `.cjs` | Script Module | CommonJS Module | +| `.json` | JSON Data | JSON Data | +| `.yml` | JSON Data | YAML Data | +| `.yaml` | JSON Data | YAML Data | +| `.css` | classes | Style sheet | +| `.eot` | URL Path | Font | +| `.ttf` | URL Path | Font | +| `.woff2` | URL Path | Font | +| `.woff` | URL Path | Font | +| `.bmp` | URL Path | Image | +| `.gif` | URL Path | Image | +| `.jpeg` | URL Path | Image | +| `.jpg` | URL Path | Image | +| `.png` | URL Path | Image | +| `.svg` | URL Path | Image | +| `.md` | URL Path | Markdown File | + +## ECMAScript Modules + +The Backstage tooling supports [ECMAScript modules (ESM)](https://nodejs.org/docs/latest-v22.x/api/esm.html) in Node.js packages. This includes support for all the script module file extensions listed above during local development, in built packages, in tests, and during type checking. [Dynamic imports](https://nodejs.org/docs/latest-v22.x/api/esm.html#import-expressions) can be used to load ESM-only packages from CommonJS and vice versa. There are however a couple of limitations to be aware of: + +- Declaring a package as `"type": "module"` in `package.json` is supported, but in tests it will cause all local transitive dependencies to also be treated as ESM, regardless of whether they declare `"type": "module"` or not. +- Node.js has an [ESM interoperability layer with CommonJS](https://nodejs.org/docs/latest-v22.x/api/esm.html#interoperability-with-commonjs) that allows for imports from ESM to identify named exports in CommonJS packages. This interoperability layer is **only** enabled when importing packages with a `.cts` or `.cjs` extension. This is because the interoperability layer is not fully compatible with the NPM ecosystem, and would break package if it was enabled for `.js` files. +- Dynamic imports of CommonJS packages will vary in shape depending on the runtime, i.e. test vs local development, etc. It is therefore recommended to avoid dynamic imports of CommonJS packages and instead use `require`, or to use the explicit CommonJS extensions as mentioned above. If you do need to dynamically import CommonJS packages, avoid using `default` exports, as the shape of them vary across different environments and you would otherwise need to manually unwrap the import based on the shape of the module object. ## Jest Configuration From 479f9a068d8b77b0a33531f623dcd06cd3f00341 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 26 Dec 2024 18:52:41 +0100 Subject: [PATCH 064/420] cli: restore interop compat mode for cjs build Signed-off-by: Patrik Oldsberg --- packages/cli/src/lib/builder/config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/cli/src/lib/builder/config.ts b/packages/cli/src/lib/builder/config.ts index 3126383308..3b55a5a451 100644 --- a/packages/cli/src/lib/builder/config.ts +++ b/packages/cli/src/lib/builder/config.ts @@ -193,6 +193,7 @@ export async function makeRollupConfigs( sourcemap: true, preserveModules: true, preserveModulesRoot: `${targetDir}/src`, + interop: 'compat', exports: 'named', plugins: [multiOutputFormat()], }; From cb76663f9a13bc1eab49167de5fee25f8005414e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 26 Dec 2024 21:25:37 +0100 Subject: [PATCH 065/420] .changesets: add changesets for ESM support Signed-off-by: Patrik Oldsberg --- .changeset/afraid-experts-explain.md | 5 +++++ .changeset/beige-dingos-destroy.md | 17 +++++++++++++++++ .changeset/quiet-phones-sell.md | 5 +++++ 3 files changed, 27 insertions(+) create mode 100644 .changeset/afraid-experts-explain.md create mode 100644 .changeset/beige-dingos-destroy.md create mode 100644 .changeset/quiet-phones-sell.md diff --git a/.changeset/afraid-experts-explain.md b/.changeset/afraid-experts-explain.md new file mode 100644 index 0000000000..38eb3709d0 --- /dev/null +++ b/.changeset/afraid-experts-explain.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli-node': patch +--- + +Added `type` field to `BackstagePackageJson` type. diff --git a/.changeset/beige-dingos-destroy.md b/.changeset/beige-dingos-destroy.md new file mode 100644 index 0000000000..917f03bb8d --- /dev/null +++ b/.changeset/beige-dingos-destroy.md @@ -0,0 +1,17 @@ +--- +'@backstage/cli': minor +--- + +**BREAKING**: Add support for native ESM in Node.js code. This changes the behavior of dynamic import expressions in Node.js code. Typically this can be fixed by replacing `import(...)` with `require(...)`, with an `as typeof import(...)` cast if needed for types. This is because dynamic imports will no longer be transformed to `require(...)` calls, but instead be left as this. This in turn allows you to load ESM modules from CommonJS code using `import(...)`. + +This change adds support for the following in Node.js packages, across type checking, package builds, runtime transforms and Jest tests: + +- Dynamic imports that load ESM modules from CommonJS code. +- Both `.mjs` and `.mts` files as explicit ESM files, as well as `.cjs` and `.cts` as explicit CommonJS files. +- Support for the `"type": "module"` field in `package.json` to indicate that the package is an ESM package. + +There are a few caveats to be aware of: + +- Declaring a package as `"type": "module"` in `package.json` is supported, but in tests it will cause all local transitive dependencies to also be treated as ESM, regardless of whether they declare `"type": "module"` or not. +- Node.js has an [ESM interoperability layer with CommonJS](https://nodejs.org/docs/latest-v22.x/api/esm.html#interoperability-with-commonjs) that allows for imports from ESM to identify named exports in CommonJS packages. This interoperability layer is **only** enabled when importing packages with a `.cts` or `.cjs` extension. This is because the interoperability layer is not fully compatible with the NPM ecosystem, and would break package if it was enabled for `.js` files. +- Dynamic imports of CommonJS packages will vary in shape depending on the runtime, i.e. test vs local development, etc. It is therefore recommended to avoid dynamic imports of CommonJS packages and instead use `require`, or to use the explicit CommonJS extensions as mentioned above. If you do need to dynamically import CommonJS packages, avoid using `default` exports, as the shape of them vary across different environments and you would otherwise need to manually unwrap the import based on the shape of the module object. diff --git a/.changeset/quiet-phones-sell.md b/.changeset/quiet-phones-sell.md new file mode 100644 index 0000000000..f41f5d685e --- /dev/null +++ b/.changeset/quiet-phones-sell.md @@ -0,0 +1,5 @@ +--- +'@backstage/repo-tools': patch +--- + +Internal refactor to support native ESM. From c1775a1b3c0f5aa21c4d1475788bb8cc90c76242 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 26 Dec 2024 21:35:40 +0100 Subject: [PATCH 066/420] document need to run ESM tests with --experimental-vm-modules Signed-off-by: Patrik Oldsberg --- .changeset/beige-dingos-destroy.md | 1 + docs/tooling/cli/02-build-system.md | 1 + 2 files changed, 2 insertions(+) diff --git a/.changeset/beige-dingos-destroy.md b/.changeset/beige-dingos-destroy.md index 917f03bb8d..fdd8bfe415 100644 --- a/.changeset/beige-dingos-destroy.md +++ b/.changeset/beige-dingos-destroy.md @@ -12,6 +12,7 @@ This change adds support for the following in Node.js packages, across type chec There are a few caveats to be aware of: +- To enable support for native ESM in tests, you need to run the tests with the `--experimental-vm-module` flag enabled, typically via `NODE_OPTIONS='--experimental-vm-modules'`. - Declaring a package as `"type": "module"` in `package.json` is supported, but in tests it will cause all local transitive dependencies to also be treated as ESM, regardless of whether they declare `"type": "module"` or not. - Node.js has an [ESM interoperability layer with CommonJS](https://nodejs.org/docs/latest-v22.x/api/esm.html#interoperability-with-commonjs) that allows for imports from ESM to identify named exports in CommonJS packages. This interoperability layer is **only** enabled when importing packages with a `.cts` or `.cjs` extension. This is because the interoperability layer is not fully compatible with the NPM ecosystem, and would break package if it was enabled for `.js` files. - Dynamic imports of CommonJS packages will vary in shape depending on the runtime, i.e. test vs local development, etc. It is therefore recommended to avoid dynamic imports of CommonJS packages and instead use `require`, or to use the explicit CommonJS extensions as mentioned above. If you do need to dynamically import CommonJS packages, avoid using `default` exports, as the shape of them vary across different environments and you would otherwise need to manually unwrap the import based on the shape of the module object. diff --git a/docs/tooling/cli/02-build-system.md b/docs/tooling/cli/02-build-system.md index c9b2fec5ec..5e82da4f9b 100644 --- a/docs/tooling/cli/02-build-system.md +++ b/docs/tooling/cli/02-build-system.md @@ -513,6 +513,7 @@ of all supported file extensions: The Backstage tooling supports [ECMAScript modules (ESM)](https://nodejs.org/docs/latest-v22.x/api/esm.html) in Node.js packages. This includes support for all the script module file extensions listed above during local development, in built packages, in tests, and during type checking. [Dynamic imports](https://nodejs.org/docs/latest-v22.x/api/esm.html#import-expressions) can be used to load ESM-only packages from CommonJS and vice versa. There are however a couple of limitations to be aware of: +- To enable support for native ESM in tests, you need to run the tests with the `--experimental-vm-module` flag enabled, typically via `NODE_OPTIONS='--experimental-vm-modules'`. - Declaring a package as `"type": "module"` in `package.json` is supported, but in tests it will cause all local transitive dependencies to also be treated as ESM, regardless of whether they declare `"type": "module"` or not. - Node.js has an [ESM interoperability layer with CommonJS](https://nodejs.org/docs/latest-v22.x/api/esm.html#interoperability-with-commonjs) that allows for imports from ESM to identify named exports in CommonJS packages. This interoperability layer is **only** enabled when importing packages with a `.cts` or `.cjs` extension. This is because the interoperability layer is not fully compatible with the NPM ecosystem, and would break package if it was enabled for `.js` files. - Dynamic imports of CommonJS packages will vary in shape depending on the runtime, i.e. test vs local development, etc. It is therefore recommended to avoid dynamic imports of CommonJS packages and instead use `require`, or to use the explicit CommonJS extensions as mentioned above. If you do need to dynamically import CommonJS packages, avoid using `default` exports, as the shape of them vary across different environments and you would otherwise need to manually unwrap the import based on the shape of the module object. From e6c049add0ad0e6f5d8e097b4a80571e703223ce Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 26 Dec 2024 21:44:19 +0100 Subject: [PATCH 067/420] cil: removed dead code in node transform Signed-off-by: Patrik Oldsberg --- packages/cli/config/nodeTransformHooks.mjs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/cli/config/nodeTransformHooks.mjs b/packages/cli/config/nodeTransformHooks.mjs index b7f3248546..06bfcf4e30 100644 --- a/packages/cli/config/nodeTransformHooks.mjs +++ b/packages/cli/config/nodeTransformHooks.mjs @@ -69,11 +69,6 @@ export async function resolve(specifier, context, nextResolve) { return withDetectedModuleType(await nextResolve(specifier, context)); } - // Imports with exact file extensions are handled by the default resolver - // if (ext !== '') { - // return withDetectedModuleType(await nextResolve(specifier, context)); - // } - // The rest of this function handles the case of resolving imports that do not // specify any extension and might point to a directory with an `index.*` // file. We resolve those using the same logic as most JS bundlers would, with From a8dc7f2a7a431e66787b9b70ede7587291e6b14a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 26 Dec 2024 21:51:05 +0100 Subject: [PATCH 068/420] cli: explicit module type fallback for Node.js 22 Signed-off-by: Patrik Oldsberg --- packages/cli/config/nodeTransformHooks.mjs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/cli/config/nodeTransformHooks.mjs b/packages/cli/config/nodeTransformHooks.mjs index 06bfcf4e30..7d1186561f 100644 --- a/packages/cli/config/nodeTransformHooks.mjs +++ b/packages/cli/config/nodeTransformHooks.mjs @@ -112,6 +112,10 @@ async function withDetectedModuleType(resolved) { if (resolved.format) { return resolved; } + // Happens in Node.js v22 when there's a package.json without an explicit "type" field. Use the default. + if (resolved.format === null) { + return { ...resolved, format: DEFAULT_MODULE_FORMAT }; + } const ext = extname(resolved.url); From 95491d2fa4608708027ea343f50bef9b687ba942 Mon Sep 17 00:00:00 2001 From: darylgraham Date: Tue, 5 Nov 2024 23:16:02 +0000 Subject: [PATCH 069/420] Extend Azure Org custom transformer docs to be more end-to-end Signed-off-by: darylgraham --- docs/integrations/azure/org.md | 139 +++++++++++++++++++++++++++++++-- 1 file changed, 132 insertions(+), 7 deletions(-) diff --git a/docs/integrations/azure/org.md b/docs/integrations/azure/org.md index e6fb69a520..6e8b9c84a3 100644 --- a/docs/integrations/azure/org.md +++ b/docs/integrations/azure/org.md @@ -258,21 +258,96 @@ The `myUserTransformer`, `myGroupTransformer`, `myOrganizationTransformer`, and The following provides an example of each kind of transformer. We recommend creating a `transformers.ts` file in your `packages/backend/src` folder for these. +First, lets set up the basic structure of the file, with functions for each kind of transformer that simply passes through the default transformer unchanged. + ```ts title="packages/backend/src/transformers.ts" import * as MicrosoftGraph from '@microsoft/microsoft-graph-types'; import { defaultGroupTransformer, defaultUserTransformer, defaultOrganizationTransformer, + microsoftGraphOrgEntityProviderTransformExtensionPoint, MicrosoftGraphProviderConfig, } from '@backstage/plugin-catalog-backend-module-msgraph'; import { GroupEntity, UserEntity } from '@backstage/catalog-model'; +import { createBackendModule } from '@backstage/backend-plugin-api'; -// This group transformer completely replaces the built in logic with custom logic. +// The Group transformer transforms Groups that are ingested from MS Graph export async function myGroupTransformer( group: MicrosoftGraph.Group, groupPhoto?: string, ): Promise { + const backstageGroup = await defaultGroupTransformer(group, groupPhoto); + return backstageGroup; +} + +// The User transformer transforms Users that are ingested from MS Graph +export async function myUserTransformer( + graphUser: MicrosoftGraph.User, + userPhoto?: string, +): Promise { + const backstageUser = await defaultUserTransformer(graphUser, userPhoto); + return backstageUser; +} + +// The Organization transformer transforms the root MS Graph Organization into a Group +export async function myOrganizationTransformer( + graphOrganization: MicrosoftGraph.Organization, +): Promise { + const backstageOrg = await defaultOrganizationTransformer(graphOrganization); + return backstageOrg; +} + +// The Provider Config transformer enables modification of the plugin config +export async function myProviderConfigTransformer( + provider: MicrosoftGraphProviderConfig, +): Promise { + return provider; +} + +// Wrapping these functions in a Module allows us to inject them into the Catalog plugin easily +export const myMsgraphTransformersModule = createBackendModule({ + pluginId: 'catalog', + moduleId: 'msgraph-org', + register(reg) { + reg.registerInit({ + deps: { + microsoftGraphTransformers: + microsoftGraphOrgEntityProviderTransformExtensionPoint, + }, + async init({ microsoftGraphTransformers }) { + // Set the transformers to our custom functions + microsoftGraphTransformers.setUserTransformer(myUserTransformer); + microsoftGraphTransformers.setGroupTransformer(myGroupTransformer); + microsoftGraphTransformers.setOrganizationTransformer( + myOrganizationTransformer, + ); + microsoftGraphTransformers.setProviderConfigTransformer( + myProviderConfigTransformer, + ); + }, + }); + }, +}); + +// Export a default to make importing into the backend simpler +export default myMsgraphTransformersModule; +``` + +Now lets customize each of the providers to suit our needs. + +The Group Transformer will have the default logic completely removed and replaced with our custom logic: + +```ts +export async function myGroupTransformer( + group: MicrosoftGraph.Group, + groupPhoto?: string, +): Promise { + // highlight-remove-start + const backstageGroup = await defaultGroupTransformer(group, groupPhoto); + return backstageGroup; + // highlight-remove-end + // highlight-add-start return { apiVersion: 'backstage.io/v1alpha1', kind: 'Group', @@ -285,40 +360,90 @@ export async function myGroupTransformer( children: [], }, }; + // highlight-add-end } +``` -// This user transformer makes use of the built in logic, but also sets the description field +The User Transformer makes use of the built-in logic, but also modifies the username and sets a description + +```ts export async function myUserTransformer( graphUser: MicrosoftGraph.User, userPhoto?: string, ): Promise { const backstageUser = await defaultUserTransformer(graphUser, userPhoto); - + // highlight-add-start + // Make sure the default transformer returned an entity if (backstageUser) { - backstageUser.metadata.description = 'Loaded from Microsoft Entra ID'; + // Update the description to make it obvious where this entity came from + backstageUser.metadata.description = + 'Loaded from Microsoft Entra ID via MyCustomUserTransformer'; + + // The default transformer sets the username to the email address with invalid characters subbed out: 'user_domain.com' + // Set the username to the local part of the email address in lowercase without the domain + const newName = backstageUser.metadata.name.split('_')[0].toLowerCase(); + backstageUser.metadata.name = newName; + + return backstageUser; } - + return undefined; + // highlight-add-end + // highlight-remove-start return backstageUser; + // highlight-remove-end } +``` -// Example organization transformer that removes the organization group completely +The Organization Transformer removes the organization group completely by returning undefined + +```ts export async function myOrganizationTransformer( graphOrganization: MicrosoftGraph.Organization, ): Promise { + // highlight-remove-start + const backstageOrg = await defaultOrganizationTransformer(graphOrganization); + return backstageOrg; + // highlight-remove-end + // highlight-add-start return undefined; + // highlight-add-end } +``` -// Example config transformer that expands the group filter to also include 'azure-group-a' +The Config Transformer expands the group filter to also include 'azure-group-a' + +```ts export async function myProviderConfigTransformer( provider: MicrosoftGraphProviderConfig, ): Promise { + // highlight-add-start if (!provider.groupFilter?.includes('azure-group-a')) { provider.groupFilter = `${provider.groupFilter} or displayName eq 'azure-group-a'`; } + // highlight-add-end return provider; } ``` +Now we just need to add our new module to the Backend. + +```ts +// packages/backend/src/index.ts +// Your file will have more than this in it + +const backend = createBackend(); + +... + +// highlight-add-start +backend.add(import('./transformers')); +// highlight-add-end + +... + +backend.start(); +``` + ## Troubleshooting ### No data From b84fde2cc530d46a3b8410ea1a66cffd75ce3181 Mon Sep 17 00:00:00 2001 From: Daryl Graham Date: Fri, 29 Nov 2024 20:19:56 +1000 Subject: [PATCH 070/420] Update docs/integrations/azure/org.md Co-authored-by: Andre Wanlin <67169551+awanlin@users.noreply.github.com> Signed-off-by: Daryl Graham Signed-off-by: darylgraham --- docs/integrations/azure/org.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/azure/org.md b/docs/integrations/azure/org.md index 6e8b9c84a3..aab6c47b5d 100644 --- a/docs/integrations/azure/org.md +++ b/docs/integrations/azure/org.md @@ -260,7 +260,7 @@ The following provides an example of each kind of transformer. We recommend crea First, lets set up the basic structure of the file, with functions for each kind of transformer that simply passes through the default transformer unchanged. -```ts title="packages/backend/src/transformers.ts" +```ts title="packages/backend/src/extensions/transformers.ts" import * as MicrosoftGraph from '@microsoft/microsoft-graph-types'; import { defaultGroupTransformer, From 0ede7cc8fcd123e20f6e3f9aab1fde806e8cd9ab Mon Sep 17 00:00:00 2001 From: Daryl Graham Date: Fri, 29 Nov 2024 20:22:25 +1000 Subject: [PATCH 071/420] Apply suggestions from code review Co-authored-by: Andre Wanlin <67169551+awanlin@users.noreply.github.com> Signed-off-by: Daryl Graham Signed-off-by: darylgraham --- docs/integrations/azure/org.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/integrations/azure/org.md b/docs/integrations/azure/org.md index aab6c47b5d..3773c664c2 100644 --- a/docs/integrations/azure/org.md +++ b/docs/integrations/azure/org.md @@ -306,7 +306,7 @@ export async function myProviderConfigTransformer( } // Wrapping these functions in a Module allows us to inject them into the Catalog plugin easily -export const myMsgraphTransformersModule = createBackendModule({ +export default createBackendModule({ pluginId: 'catalog', moduleId: 'msgraph-org', register(reg) { @@ -336,7 +336,7 @@ export default myMsgraphTransformersModule; Now lets customize each of the providers to suit our needs. -The Group Transformer will have the default logic completely removed and replaced with our custom logic: +This Group Transformer example will have the default logic completely removed and replaced with our custom logic: ```ts export async function myGroupTransformer( @@ -364,7 +364,7 @@ export async function myGroupTransformer( } ``` -The User Transformer makes use of the built-in logic, but also modifies the username and sets a description +This User Transformer example makes use of the built-in logic, but also modifies the username and sets a description ```ts export async function myUserTransformer( @@ -394,7 +394,7 @@ export async function myUserTransformer( } ``` -The Organization Transformer removes the organization group completely by returning undefined +This Organization Transformer example removes the organization group completely by returning undefined ```ts export async function myOrganizationTransformer( @@ -410,7 +410,7 @@ export async function myOrganizationTransformer( } ``` -The Config Transformer expands the group filter to also include 'azure-group-a' +This Config Transformer example expands the group filter to also include 'azure-group-a' ```ts export async function myProviderConfigTransformer( @@ -436,7 +436,7 @@ const backend = createBackend(); ... // highlight-add-start -backend.add(import('./transformers')); +backend.add(import('./extensions/transformers')); // highlight-add-end ... From 22da1177ff4dac312ecd079b5fb18ae98396aeb3 Mon Sep 17 00:00:00 2001 From: darylgraham Date: Mon, 2 Dec 2024 00:00:26 +0000 Subject: [PATCH 072/420] Update custom transformers with logic to explain why we would make the change Signed-off-by: darylgraham --- docs/integrations/azure/org.md | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/docs/integrations/azure/org.md b/docs/integrations/azure/org.md index 3773c664c2..bc2b35d8c5 100644 --- a/docs/integrations/azure/org.md +++ b/docs/integrations/azure/org.md @@ -329,9 +329,6 @@ export default createBackendModule({ }); }, }); - -// Export a default to make importing into the backend simpler -export default myMsgraphTransformersModule; ``` Now lets customize each of the providers to suit our needs. @@ -348,15 +345,26 @@ export async function myGroupTransformer( return backstageGroup; // highlight-remove-end // highlight-add-start + // All of our groups are prefixed with the organisational unit: 'Engineering - Team A' + // We want to drop the org unit from the group name and use it for the namespace instead + const groupNameArr = group.displayName.split(' - '); + const displayName = groupNameArr[1]; + // Standardise name and namespace by replacing spaces with hyphens and converting to lowercase + const namespace = groupNameArr[0].replace(' ', '-').toLowerCase(); + const groupName = groupNameArr[1].replace(' ', '-').toLowerCase(); + return { apiVersion: 'backstage.io/v1alpha1', kind: 'Group', metadata: { - name: group.id!, + name: groupName, + description: group.description, annotations: {}, }, spec: { - type: 'Microsoft Entra ID', + type: 'team', + displayName: displayName, + email: group.mail, children: [], }, }; @@ -405,6 +413,8 @@ export async function myOrganizationTransformer( return backstageOrg; // highlight-remove-end // highlight-add-start + // The org transformer creates a group to be used as the base of the relationship tree for groups + // We don't need this to be created, so return undefined instead of an entity return undefined; // highlight-add-end } @@ -417,6 +427,8 @@ export async function myProviderConfigTransformer( provider: MicrosoftGraphProviderConfig, ): Promise { // highlight-add-start + // The filter in our config file relies on a property that has been intermittantly causing this important group to fail ingestion + // Ensure the group is always discovered by the filter if (!provider.groupFilter?.includes('azure-group-a')) { provider.groupFilter = `${provider.groupFilter} or displayName eq 'azure-group-a'`; } From 5e282bc5377508cf8c3f21c2800350eee80a5a3a Mon Sep 17 00:00:00 2001 From: darylgraham Date: Fri, 27 Dec 2024 11:59:21 +0000 Subject: [PATCH 073/420] codefence title added instead of a comment inside the block Signed-off-by: darylgraham --- docs/integrations/azure/org.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/integrations/azure/org.md b/docs/integrations/azure/org.md index bc2b35d8c5..068aa70101 100644 --- a/docs/integrations/azure/org.md +++ b/docs/integrations/azure/org.md @@ -439,8 +439,7 @@ export async function myProviderConfigTransformer( Now we just need to add our new module to the Backend. -```ts -// packages/backend/src/index.ts +```ts title="packages/backend/src/index.ts" // Your file will have more than this in it const backend = createBackend(); From aaf1c3b4d236c5796f877522d3d492d6f6f32454 Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Sun, 29 Dec 2024 16:05:51 +0100 Subject: [PATCH 074/420] First pass at creating a new website Signed-off-by: Charles de Dreuille --- packages/canon/canon-docs/.gitignore | 41 + packages/canon/canon-docs/README.md | 36 + packages/canon/canon-docs/app/favicon.ico | Bin 0 -> 25931 bytes packages/canon/canon-docs/app/globals.css | 11 + packages/canon/canon-docs/app/layout.tsx | 30 + packages/canon/canon-docs/app/page.module.css | 3 + packages/canon/canon-docs/app/page.tsx | 18 + .../canon/canon-docs/app/playground/page.tsx | 3 + .../components/Tabs/Tabs.module.css | 72 + .../canon-docs/components/Tabs/index.tsx | 84 + .../components/sidebar/Sidebar.module.css | 46 + .../canon-docs/components/sidebar/index.tsx | 32 + packages/canon/canon-docs/eslint.config.mjs | 16 + packages/canon/canon-docs/next.config.ts | 7 + packages/canon/canon-docs/package.json | 26 + packages/canon/canon-docs/public/logo.svg | 1 + packages/canon/canon-docs/tsconfig.json | 27 + packages/canon/canon-docs/yarn.lock | 3420 +++++++++++++++++ packages/canon/src/components/Icon/Icon.tsx | 2 + packages/canon/src/components/Icon/icons.ts | 4 + packages/canon/src/components/Icon/types.ts | 2 + packages/canon/src/contexts/canon.tsx | 2 + packages/canon/src/css/base.css | 4 +- packages/canon/src/css/normalize.css | 212 +- 24 files changed, 3992 insertions(+), 107 deletions(-) create mode 100644 packages/canon/canon-docs/.gitignore create mode 100644 packages/canon/canon-docs/README.md create mode 100644 packages/canon/canon-docs/app/favicon.ico create mode 100644 packages/canon/canon-docs/app/globals.css create mode 100644 packages/canon/canon-docs/app/layout.tsx create mode 100644 packages/canon/canon-docs/app/page.module.css create mode 100644 packages/canon/canon-docs/app/page.tsx create mode 100644 packages/canon/canon-docs/app/playground/page.tsx create mode 100644 packages/canon/canon-docs/components/Tabs/Tabs.module.css create mode 100644 packages/canon/canon-docs/components/Tabs/index.tsx create mode 100644 packages/canon/canon-docs/components/sidebar/Sidebar.module.css create mode 100644 packages/canon/canon-docs/components/sidebar/index.tsx create mode 100644 packages/canon/canon-docs/eslint.config.mjs create mode 100644 packages/canon/canon-docs/next.config.ts create mode 100644 packages/canon/canon-docs/package.json create mode 100644 packages/canon/canon-docs/public/logo.svg create mode 100644 packages/canon/canon-docs/tsconfig.json create mode 100644 packages/canon/canon-docs/yarn.lock diff --git a/packages/canon/canon-docs/.gitignore b/packages/canon/canon-docs/.gitignore new file mode 100644 index 0000000000..5ef6a52078 --- /dev/null +++ b/packages/canon/canon-docs/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/packages/canon/canon-docs/README.md b/packages/canon/canon-docs/README.md new file mode 100644 index 0000000000..e215bc4ccf --- /dev/null +++ b/packages/canon/canon-docs/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/packages/canon/canon-docs/app/favicon.ico b/packages/canon/canon-docs/app/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..718d6fea4835ec2d246af9800eddb7ffb276240c GIT binary patch literal 25931 zcmeHv30#a{`}aL_*G&7qml|y<+KVaDM2m#dVr!KsA!#An?kSQM(q<_dDNCpjEux83 zLb9Z^XxbDl(w>%i@8hT6>)&Gu{h#Oeyszu?xtw#Zb1mO{pgX9699l+Qppw7jXaYf~-84xW z)w4x8?=youko|}Vr~(D$UXIbiXABHh`p1?nn8Po~fxRJv}|0e(BPs|G`(TT%kKVJAdg5*Z|x0leQq0 zkdUBvb#>9F()jo|T~kx@OM8$9wzs~t2l;K=woNssA3l6|sx2r3+kdfVW@e^8e*E}v zA1y5{bRi+3Z`uD3{F7LgFJDdvm;nJilkzDku>BwXH(8ItVCXk*-lSJnR?-2UN%hJ){&rlvg`CDTj z)Bzo!3v7Ou#83zEDEFcKt(f1E0~=rqeEbTnMvWR#{+9pg%7G8y>u1OVRUSoox-ovF z2Ydma(;=YuBY(eI|04{hXzZD6_f(v~H;C~y5=DhAC{MMS>2fm~1H_t2$56pc$NH8( z5bH|<)71dV-_oCHIrzrT`2s-5w_+2CM0$95I6X8p^r!gHp+j_gd;9O<1~CEQQGS8) zS9Qh3#p&JM-G8rHekNmKVewU;pJRcTAog68KYo^dRo}(M>36U4Us zfgYWSiHZL3;lpWT=zNAW>Dh#mB!_@Lg%$ms8N-;aPqMn+C2HqZgz&9~Eu z4|Kp<`$q)Uw1R?y(~S>ePdonHxpV1#eSP1B;Ogo+-Pk}6#0GsZZ5!||ev2MGdh}_m z{DeR7?0-1^zVs&`AV6Vt;r3`I`OI_wgs*w=eO%_#7Kepl{B@xiyCANc(l zzIyd4y|c6PXWq9-|KM8(zIk8LPk(>a)zyFWjhT!$HJ$qX1vo@d25W<fvZQ2zUz5WRc(UnFMKHwe1| zWmlB1qdbiA(C0jmnV<}GfbKtmcu^2*P^O?MBLZKt|As~ge8&AAO~2K@zbXelK|4T<{|y4`raF{=72kC2Kn(L4YyenWgrPiv z@^mr$t{#X5VuIMeL!7Ab6_kG$&#&5p*Z{+?5U|TZ`B!7llpVmp@skYz&n^8QfPJzL z0G6K_OJM9x+Wu2gfN45phANGt{7=C>i34CV{Xqlx(fWpeAoj^N0Biu`w+MVcCUyU* zDZuzO0>4Z6fbu^T_arWW5n!E45vX8N=bxTVeFoep_G#VmNlQzAI_KTIc{6>c+04vr zx@W}zE5JNSU>!THJ{J=cqjz+4{L4A{Ob9$ZJ*S1?Ggg3klFp!+Y1@K+pK1DqI|_gq z5ZDXVpge8-cs!o|;K73#YXZ3AShj50wBvuq3NTOZ`M&qtjj#GOFfgExjg8Gn8>Vq5 z`85n+9|!iLCZF5$HJ$Iu($dm?8~-ofu}tEc+-pyke=3!im#6pk_Wo8IA|fJwD&~~F zc16osQ)EBo58U7XDuMexaPRjU@h8tXe%S{fA0NH3vGJFhuyyO!Uyl2^&EOpX{9As0 zWj+P>{@}jxH)8|r;2HdupP!vie{sJ28b&bo!8`D^x}TE$%zXNb^X1p@0PJ86`dZyj z%ce7*{^oo+6%&~I!8hQy-vQ7E)0t0ybH4l%KltWOo~8cO`T=157JqL(oq_rC%ea&4 z2NcTJe-HgFjNg-gZ$6!Y`SMHrlj}Etf7?r!zQTPPSv}{so2e>Fjs1{gzk~LGeesX%r(Lh6rbhSo_n)@@G-FTQy93;l#E)hgP@d_SGvyCp0~o(Y;Ee8{ zdVUDbHm5`2taPUOY^MAGOw*>=s7=Gst=D+p+2yON!0%Hk` zz5mAhyT4lS*T3LS^WSxUy86q&GnoHxzQ6vm8)VS}_zuqG?+3td68_x;etQAdu@sc6 zQJ&5|4(I?~3d-QOAODHpZ=hlSg(lBZ!JZWCtHHSj`0Wh93-Uk)_S%zsJ~aD>{`A0~ z9{AG(e|q3g5B%wYKRxiL2Y$8(4w6bzchKuloQW#e&S3n+P- z8!ds-%f;TJ1>)v)##>gd{PdS2Oc3VaR`fr=`O8QIO(6(N!A?pr5C#6fc~Ge@N%Vvu zaoAX2&(a6eWy_q&UwOhU)|P3J0Qc%OdhzW=F4D|pt0E4osw;%<%Dn58hAWD^XnZD= z>9~H(3bmLtxpF?a7su6J7M*x1By7YSUbxGi)Ot0P77`}P3{)&5Un{KD?`-e?r21!4vTTnN(4Y6Lin?UkSM z`MXCTC1@4A4~mvz%Rh2&EwY))LeoT=*`tMoqcEXI>TZU9WTP#l?uFv+@Dn~b(>xh2 z;>B?;Tz2SR&KVb>vGiBSB`@U7VIWFSo=LDSb9F{GF^DbmWAfpms8Sx9OX4CnBJca3 zlj9(x!dIjN?OG1X4l*imJNvRCk}F%!?SOfiOq5y^mZW)jFL@a|r-@d#f7 z2gmU8L3IZq0ynIws=}~m^#@&C%J6QFo~Mo4V`>v7MI-_!EBMMtb%_M&kvAaN)@ZVw z+`toz&WG#HkWDjnZE!6nk{e-oFdL^$YnbOCN}JC&{$#$O27@|Tn-skXr)2ml2~O!5 zX+gYoxhoc7qoU?C^3~&!U?kRFtnSEecWuH0B0OvLodgUAi}8p1 zrO6RSXHH}DMc$&|?D004DiOVMHV8kXCP@7NKB zgaZq^^O<7PoKEp72kby@W0Z!Y*Ay{&vfg#C&gG@YVR9g?FEocMUi1gSN$+V+ayF45{a zuDZDTN}mS|;BO%gEf}pjBfN2-gIrU#G5~cucA;dokXW89%>AyXJJI z9X4UlIWA|ZYHgbI z5?oFk@A=Ik7lrEQPDH!H+b`7_Y~aDb_qa=B2^Y&Ow41cU=4WDd40dp5(QS-WMN-=Y z9g;6_-JdNU;|6cPwf$ak*aJIcwL@1n$#l~zi{c{EW?T;DaW*E8DYq?Umtz{nJ&w-M zEMyTDrC&9K$d|kZe2#ws6)L=7K+{ zQw{XnV6UC$6-rW0emqm8wJoeZK)wJIcV?dST}Z;G0Arq{dVDu0&4kd%N!3F1*;*pW zR&qUiFzK=@44#QGw7k1`3t_d8&*kBV->O##t|tonFc2YWrL7_eqg+=+k;!F-`^b8> z#KWCE8%u4k@EprxqiV$VmmtiWxDLgnGu$Vs<8rppV5EajBXL4nyyZM$SWVm!wnCj-B!Wjqj5-5dNXukI2$$|Bu3Lrw}z65Lc=1G z^-#WuQOj$hwNGG?*CM_TO8Bg-1+qc>J7k5c51U8g?ZU5n?HYor;~JIjoWH-G>AoUP ztrWWLbRNqIjW#RT*WqZgPJXU7C)VaW5}MiijYbABmzoru6EmQ*N8cVK7a3|aOB#O& zBl8JY2WKfmj;h#Q!pN%9o@VNLv{OUL?rixHwOZuvX7{IJ{(EdPpuVFoQqIOa7giLVkBOKL@^smUA!tZ1CKRK}#SSM)iQHk)*R~?M!qkCruaS!#oIL1c z?J;U~&FfH#*98^G?i}pA{ z9Jg36t4=%6mhY(quYq*vSxptes9qy|7xSlH?G=S@>u>Ebe;|LVhs~@+06N<4CViBk zUiY$thvX;>Tby6z9Y1edAMQaiH zm^r3v#$Q#2T=X>bsY#D%s!bhs^M9PMAcHbCc0FMHV{u-dwlL;a1eJ63v5U*?Q_8JO zT#50!RD619#j_Uf))0ooADz~*9&lN!bBDRUgE>Vud-i5ck%vT=r^yD*^?Mp@Q^v+V zG#-?gKlr}Eeqifb{|So?HM&g91P8|av8hQoCmQXkd?7wIJwb z_^v8bbg`SAn{I*4bH$u(RZ6*xUhuA~hc=8czK8SHEKTzSxgbwi~9(OqJB&gwb^l4+m`k*Q;_?>Y-APi1{k zAHQ)P)G)f|AyjSgcCFps)Fh6Bca*Xznq36!pV6Az&m{O8$wGFD? zY&O*3*J0;_EqM#jh6^gMQKpXV?#1?>$ml1xvh8nSN>-?H=V;nJIwB07YX$e6vLxH( zqYwQ>qxwR(i4f)DLd)-$P>T-no_c!LsN@)8`e;W@)-Hj0>nJ-}Kla4-ZdPJzI&Mce zv)V_j;(3ERN3_@I$N<^|4Lf`B;8n+bX@bHbcZTopEmDI*Jfl)-pFDvo6svPRoo@(x z);_{lY<;);XzT`dBFpRmGrr}z5u1=pC^S-{ce6iXQlLGcItwJ^mZx{m$&DA_oEZ)B{_bYPq-HA zcH8WGoBG(aBU_j)vEy+_71T34@4dmSg!|M8Vf92Zj6WH7Q7t#OHQqWgFE3ARt+%!T z?oLovLVlnf?2c7pTc)~cc^($_8nyKwsN`RA-23ed3sdj(ys%pjjM+9JrctL;dy8a( z@en&CQmnV(()bu|Y%G1-4a(6x{aLytn$T-;(&{QIJB9vMox11U-1HpD@d(QkaJdEb zG{)+6Dos_L+O3NpWo^=gR?evp|CqEG?L&Ut#D*KLaRFOgOEK(Kq1@!EGcTfo+%A&I z=dLbB+d$u{sh?u)xP{PF8L%;YPPW53+@{>5W=Jt#wQpN;0_HYdw1{ksf_XhO4#2F= zyPx6Lx2<92L-;L5PD`zn6zwIH`Jk($?Qw({erA$^bC;q33hv!d!>%wRhj# zal^hk+WGNg;rJtb-EB(?czvOM=H7dl=vblBwAv>}%1@{}mnpUznfq1cE^sgsL0*4I zJ##!*B?=vI_OEVis5o+_IwMIRrpQyT_Sq~ZU%oY7c5JMIADzpD!Upz9h@iWg_>>~j zOLS;wp^i$-E?4<_cp?RiS%Rd?i;f*mOz=~(&3lo<=@(nR!_Rqiprh@weZlL!t#NCc zO!QTcInq|%#>OVgobj{~ixEUec`E25zJ~*DofsQdzIa@5^nOXj2T;8O`l--(QyU^$t?TGY^7#&FQ+2SS3B#qK*k3`ye?8jUYSajE5iBbJls75CCc(m3dk{t?- zopcER9{Z?TC)mk~gpi^kbbu>b-+a{m#8-y2^p$ka4n60w;Sc2}HMf<8JUvhCL0B&Btk)T`ctE$*qNW8L$`7!r^9T+>=<=2qaq-;ll2{`{Rg zc5a0ZUI$oG&j-qVOuKa=*v4aY#IsoM+1|c4Z)<}lEDvy;5huB@1RJPquU2U*U-;gu z=En2m+qjBzR#DEJDO`WU)hdd{Vj%^0V*KoyZ|5lzV87&g_j~NCjwv0uQVqXOb*QrQ zy|Qn`hxx(58c70$E;L(X0uZZ72M1!6oeg)(cdKO ze0gDaTz+ohR-#d)NbAH4x{I(21yjwvBQfmpLu$)|m{XolbgF!pmsqJ#D}(ylp6uC> z{bqtcI#hT#HW=wl7>p!38sKsJ`r8}lt-q%Keqy%u(xk=yiIJiUw6|5IvkS+#?JTBl z8H5(Q?l#wzazujH!8o>1xtn8#_w+397*_cy8!pQGP%K(Ga3pAjsaTbbXJlQF_+m+-UpUUent@xM zg%jqLUExj~o^vQ3Gl*>wh=_gOr2*|U64_iXb+-111aH}$TjeajM+I20xw(((>fej-@CIz4S1pi$(#}P7`4({6QS2CaQS4NPENDp>sAqD z$bH4KGzXGffkJ7R>V>)>tC)uax{UsN*dbeNC*v}#8Y#OWYwL4t$ePR?VTyIs!wea+ z5Urmc)X|^`MG~*dS6pGSbU+gPJoq*^a=_>$n4|P^w$sMBBy@f*Z^Jg6?n5?oId6f{ z$LW4M|4m502z0t7g<#Bx%X;9<=)smFolV&(V^(7Cv2-sxbxopQ!)*#ZRhTBpx1)Fc zNm1T%bONzv6@#|dz(w02AH8OXe>kQ#1FMCzO}2J_mST)+ExmBr9cva-@?;wnmWMOk z{3_~EX_xadgJGv&H@zK_8{(x84`}+c?oSBX*Ge3VdfTt&F}yCpFP?CpW+BE^cWY0^ zb&uBN!Ja3UzYHK-CTyA5=L zEMW{l3Usky#ly=7px648W31UNV@K)&Ub&zP1c7%)`{);I4b0Q<)B}3;NMG2JH=X$U zfIW4)4n9ZM`-yRj67I)YSLDK)qfUJ_ij}a#aZN~9EXrh8eZY2&=uY%2N0UFF7<~%M zsB8=erOWZ>Ct_#^tHZ|*q`H;A)5;ycw*IcmVxi8_0Xk}aJA^ath+E;xg!x+As(M#0=)3!NJR6H&9+zd#iP(m0PIW8$ z1Y^VX`>jm`W!=WpF*{ioM?C9`yOR>@0q=u7o>BP-eSHqCgMDj!2anwH?s%i2p+Q7D zzszIf5XJpE)IG4;d_(La-xenmF(tgAxK`Y4sQ}BSJEPs6N_U2vI{8=0C_F?@7<(G; zo$~G=8p+076G;`}>{MQ>t>7cm=zGtfbdDXm6||jUU|?X?CaE?(<6bKDYKeHlz}DA8 zXT={X=yp_R;HfJ9h%?eWvQ!dRgz&Su*JfNt!Wu>|XfU&68iRikRrHRW|ZxzRR^`eIGt zIeiDgVS>IeExKVRWW8-=A=yA`}`)ZkWBrZD`hpWIxBGkh&f#ijr449~m`j6{4jiJ*C!oVA8ZC?$1RM#K(_b zL9TW)kN*Y4%^-qPpMP7d4)o?Nk#>aoYHT(*g)qmRUb?**F@pnNiy6Fv9rEiUqD(^O zzyS?nBrX63BTRYduaG(0VVG2yJRe%o&rVrLjbxTaAFTd8s;<<@Qs>u(<193R8>}2_ zuwp{7;H2a*X7_jryzriZXMg?bTuegABb^87@SsKkr2)0Gyiax8KQWstw^v#ix45EVrcEhr>!NMhprl$InQMzjSFH54x5k9qHc`@9uKQzvL4ihcq{^B zPrVR=o_ic%Y>6&rMN)hTZsI7I<3&`#(nl+3y3ys9A~&^=4?PL&nd8)`OfG#n zwAMN$1&>K++c{^|7<4P=2y(B{jJsQ0a#U;HTo4ZmWZYvI{+s;Td{Yzem%0*k#)vjpB zia;J&>}ICate44SFYY3vEelqStQWFihx%^vQ@Do(sOy7yR2@WNv7Y9I^yL=nZr3mb zXKV5t@=?-Sk|b{XMhA7ZGB@2hqsx}4xwCW!in#C zI@}scZlr3-NFJ@NFaJlhyfcw{k^vvtGl`N9xSo**rDW4S}i zM9{fMPWo%4wYDG~BZ18BD+}h|GQKc-g^{++3MY>}W_uq7jGHx{mwE9fZiPCoxN$+7 zrODGGJrOkcPQUB(FD5aoS4g~7#6NR^ma7-!>mHuJfY5kTe6PpNNKC9GGRiu^L31uG z$7v`*JknQHsYB!Tm_W{a32TM099djW%5e+j0Ve_ct}IM>XLF1Ap+YvcrLV=|CKo6S zb+9Nl3_YdKP6%Cxy@6TxZ>;4&nTneadr z_ES90ydCev)LV!dN=#(*f}|ZORFdvkYBni^aLbUk>BajeWIOcmHP#8S)*2U~QKI%S zyrLmtPqb&TphJ;>yAxri#;{uyk`JJqODDw%(Z=2`1uc}br^V%>j!gS)D*q*f_-qf8&D;W1dJgQMlaH5er zN2U<%Smb7==vE}dDI8K7cKz!vs^73o9f>2sgiTzWcwY|BMYHH5%Vn7#kiw&eItCqa zIkR2~Q}>X=Ar8W|^Ms41Fm8o6IB2_j60eOeBB1Br!boW7JnoeX6Gs)?7rW0^5psc- zjS16yb>dFn>KPOF;imD}e!enuIniFzv}n$m2#gCCv4jM#ArwlzZ$7@9&XkFxZ4n!V zj3dyiwW4Ki2QG{@i>yuZXQizw_OkZI^-3otXC{!(lUpJF33gI60ak;Uqitp74|B6I zgg{b=Iz}WkhCGj1M=hu4#Aw173YxIVbISaoc z-nLZC*6Tgivd5V`K%GxhBsp@SUU60-rfc$=wb>zdJzXS&-5(NRRodFk;Kxk!S(O(a0e7oY=E( zAyS;Ow?6Q&XA+cnkCb{28_1N8H#?J!*$MmIwLq^*T_9-z^&UE@A(z9oGYtFy6EZef LrJugUA?W`A8`#=m literal 0 HcmV?d00001 diff --git a/packages/canon/canon-docs/app/globals.css b/packages/canon/canon-docs/app/globals.css new file mode 100644 index 0000000000..ce79569b3d --- /dev/null +++ b/packages/canon/canon-docs/app/globals.css @@ -0,0 +1,11 @@ +body { + display: flex; + flex-direction: row; + background-color: var(--canon-background); + color: var(--canon-text-primary); +} + +iframe { + border: none; + width: 100%; +} diff --git a/packages/canon/canon-docs/app/layout.tsx b/packages/canon/canon-docs/app/layout.tsx new file mode 100644 index 0000000000..8db924521a --- /dev/null +++ b/packages/canon/canon-docs/app/layout.tsx @@ -0,0 +1,30 @@ +import type { Metadata } from 'next'; +import { Sidebar } from '../components/sidebar'; +import '../../src/css/core.css'; +import '../../src/css/components.css'; +import './globals.css'; +import { CanonProvider } from '../../src/contexts/canon'; + +export const metadata: Metadata = { + title: 'Canon', + description: 'UI library for Backstage', +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + + <> + + {children} + + + + + ); +} diff --git a/packages/canon/canon-docs/app/page.module.css b/packages/canon/canon-docs/app/page.module.css new file mode 100644 index 0000000000..601dd3a93d --- /dev/null +++ b/packages/canon/canon-docs/app/page.module.css @@ -0,0 +1,3 @@ +.page { + flex: 1; +} diff --git a/packages/canon/canon-docs/app/page.tsx b/packages/canon/canon-docs/app/page.tsx new file mode 100644 index 0000000000..09b77144cf --- /dev/null +++ b/packages/canon/canon-docs/app/page.tsx @@ -0,0 +1,18 @@ +'use client'; + +import { useSearchParams } from 'next/navigation'; +import styles from './page.module.css'; + +export default function Home() { + const searchParams = useSearchParams(); + const theme = searchParams.get('theme') === 'dark' ? 'Dark' : 'Light'; + const chromaticId = '67584b7e8c2eb09c0422c27e-dmfbzicnkw'; + const chromaticUrl = `https://${chromaticId}.chromatic.com/iframe.html`; + const iframeUrl = `${chromaticUrl}?globals=theme%3A${theme}&args=&id=components-button--primary`; + + return ( +
+