diff --git a/.changeset/odd-boxes-pump.md b/.changeset/odd-boxes-pump.md new file mode 100644 index 0000000000..b0629fa428 --- /dev/null +++ b/.changeset/odd-boxes-pump.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-compat-api': patch +--- + +Improved route path normalization when converting existing route elements in `converLegacyApp`, for example handling trailing `/*` in paths. diff --git a/packages/core-compat-api/src/collectEntityPageContents.ts b/packages/core-compat-api/src/collectEntityPageContents.ts index 7cc97f0897..503642733e 100644 --- a/packages/core-compat-api/src/collectEntityPageContents.ts +++ b/packages/core-compat-api/src/collectEntityPageContents.ts @@ -25,6 +25,7 @@ import { EntityCardBlueprint, EntityContentBlueprint, } from '@backstage/plugin-catalog-react/alpha'; +import { normalizeRoutePath } from './normalizeRoutePath'; const ENTITY_SWITCH_KEY = 'core.backstage.entitySwitch'; const ENTITY_ROUTE_KEY = 'plugin.catalog.entityLayoutRoute'; @@ -114,7 +115,7 @@ export function collectEntityPageContents( name, factory(originalFactory, { apis }) { return originalFactory({ - defaultPath: pageNode.path, + defaultPath: normalizeRoutePath(pageNode.path), defaultTitle: pageNode.title, filter: mergedIf && (entity => mergedIf(entity, { apis })), loader: () => Promise.resolve(pageNode.children), diff --git a/packages/core-compat-api/src/collectLegacyRoutes.tsx b/packages/core-compat-api/src/collectLegacyRoutes.tsx index f6408b8b3f..072e6b3bb2 100644 --- a/packages/core-compat-api/src/collectLegacyRoutes.tsx +++ b/packages/core-compat-api/src/collectLegacyRoutes.tsx @@ -41,6 +41,7 @@ import { } from './convertLegacyRouteRef'; import { compatWrapper } from './compatWrapper'; import { collectEntityPageContents } from './collectEntityPageContents'; +import { normalizeRoutePath } from './normalizeRoutePath'; /* @@ -236,7 +237,7 @@ export function collectLegacyRoutes( factory(originalFactory, { inputs: _inputs }) { // todo(blam): why do we not use the inputs here? return originalFactory({ - defaultPath: path[0] === '/' ? path.slice(1) : path, + defaultPath: normalizeRoutePath(path), routeRef: routeRef ? convertLegacyRouteRef(routeRef) : undefined, loader: async () => compatWrapper( diff --git a/packages/core-compat-api/src/normalizeRoutePath.test.ts b/packages/core-compat-api/src/normalizeRoutePath.test.ts new file mode 100644 index 0000000000..9e76d92167 --- /dev/null +++ b/packages/core-compat-api/src/normalizeRoutePath.test.ts @@ -0,0 +1,36 @@ +/* + * Copyright 2025 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 { normalizeRoutePath } from './normalizeRoutePath'; + +describe('normalizeRoutePath', () => { + it('should normalize the path', () => { + expect(normalizeRoutePath('')).toBe('/'); + expect(normalizeRoutePath('/')).toBe('/'); + expect(normalizeRoutePath('/*')).toBe('/'); + expect(normalizeRoutePath('/////')).toBe('/'); + expect(normalizeRoutePath('*//*//*')).toBe('/'); + expect(normalizeRoutePath('/foo')).toBe('/foo'); + expect(normalizeRoutePath('/foo/')).toBe('/foo'); + expect(normalizeRoutePath('/foo/*')).toBe('/foo'); + expect(normalizeRoutePath('/foo/**')).toBe('/foo'); + expect(normalizeRoutePath('/foo//**')).toBe('/foo'); + expect(normalizeRoutePath('/foo/*/*')).toBe('/foo'); + expect(normalizeRoutePath('//foo/*/*')).toBe('/foo'); + expect(normalizeRoutePath('/foo/bar//*/*')).toBe('/foo/bar'); + expect(normalizeRoutePath('/foo//bar//*/*')).toBe('/foo//bar'); + }); +}); diff --git a/packages/core-compat-api/src/normalizeRoutePath.ts b/packages/core-compat-api/src/normalizeRoutePath.ts new file mode 100644 index 0000000000..7d22966362 --- /dev/null +++ b/packages/core-compat-api/src/normalizeRoutePath.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2025 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. + */ + +/** + * Normalizes the path to make sure it always starts with a single '/' and do not end with '/' or '*' unless empty + */ +export function normalizeRoutePath(path: string) { + let normalized = path; + while (normalized.endsWith('/') || normalized.endsWith('*')) { + normalized = normalized.slice(0, -1); + } + while (normalized.startsWith('/')) { + normalized = normalized.slice(1); + } + if (!normalized) { + return '/'; + } + return `/${normalized}`; +}