diff --git a/workspaces/xcmetrics/.changeset/xcmetrics-new-frontend-support.md b/workspaces/xcmetrics/.changeset/xcmetrics-new-frontend-support.md new file mode 100644 index 00000000000..98f406865db --- /dev/null +++ b/workspaces/xcmetrics/.changeset/xcmetrics-new-frontend-support.md @@ -0,0 +1,5 @@ +--- +'@backstage-community/plugin-xcmetrics': minor +--- + +Added support for the new frontend system via a `/alpha` export. diff --git a/workspaces/xcmetrics/.yarnrc.yml b/workspaces/xcmetrics/.yarnrc.yml index 26ca3465747..ca36db29b96 100644 --- a/workspaces/xcmetrics/.yarnrc.yml +++ b/workspaces/xcmetrics/.yarnrc.yml @@ -1,4 +1,4 @@ plugins: - checksum: 0cfdc882d3c1395592aa6d4690958e70ebbc3a8ee1d888d523dc9e3c74796de26f744c37df66a69212280634f422a88074ba625dce0f7886fbdf2a904a0c38a2 path: .yarn/plugins/@yarnpkg/plugin-backstage.cjs - spec: 'https://versions.backstage.io/v1/releases/1.51.0/yarn-plugin' + spec: 'https://versions.backstage.io/v1/releases/1.51.2/yarn-plugin' diff --git a/workspaces/xcmetrics/app-config.yaml b/workspaces/xcmetrics/app-config.yaml new file mode 100644 index 00000000000..c1189c162b5 --- /dev/null +++ b/workspaces/xcmetrics/app-config.yaml @@ -0,0 +1,15 @@ +app: + title: XCMetrics Example App + baseUrl: http://localhost:3000 + +organization: + name: XCMetrics Example + +backend: + baseUrl: http://localhost:7007 + listen: + port: 7007 + +proxy: + '/xcmetrics': + target: http://127.0.0.1:8080/v1 diff --git a/workspaces/xcmetrics/backstage.json b/workspaces/xcmetrics/backstage.json index bebef981695..84468dd725a 100644 --- a/workspaces/xcmetrics/backstage.json +++ b/workspaces/xcmetrics/backstage.json @@ -1,3 +1,3 @@ { - "version": "1.51.0" + "version": "1.51.2" } diff --git a/workspaces/xcmetrics/package.json b/workspaces/xcmetrics/package.json index cb051e5207a..90e430f1bf4 100644 --- a/workspaces/xcmetrics/package.json +++ b/workspaces/xcmetrics/package.json @@ -6,6 +6,7 @@ "node": "22 || 24" }, "scripts": { + "dev": "yarn workspaces foreach -A --include @backstage-community/plugin-xcmetrics --parallel -v -i run start", "start": "backstage-cli repo start", "tsc": "tsc", "tsc:full": "tsc --skipLibCheck false --incremental false", diff --git a/workspaces/xcmetrics/plugins/xcmetrics/README.md b/workspaces/xcmetrics/plugins/xcmetrics/README.md index 563c2390ef0..1cdbc0afff4 100644 --- a/workspaces/xcmetrics/plugins/xcmetrics/README.md +++ b/workspaces/xcmetrics/plugins/xcmetrics/README.md @@ -35,3 +35,26 @@ proxy: ``` Start Backstage and navigate to `/xcmetrics` to view your build metrics! + +## New Frontend System + +If you are using Backstage feature discovery, the plugin can be discovered automatically. Otherwise, you can register the alpha entry manually in your app: + +```tsx +import { createApp } from '@backstage/app-defaults'; +import xcmetricsPlugin from '@backstage-community/plugin-xcmetrics/alpha'; + +const app = createApp({ + features: [ + // ...other features + xcmetricsPlugin, + ], +}); +``` + +This plugin provides the following new frontend system extensions: + +- A page extension for the XCMetrics dashboard +- An API extension for `xcmetricsApiRef` + +Use `@backstage-community/plugin-xcmetrics/alpha` to enable the plugin through the new frontend system. diff --git a/workspaces/xcmetrics/plugins/xcmetrics/dev/DevXcmetricsApi.ts b/workspaces/xcmetrics/plugins/xcmetrics/dev/DevXcmetricsApi.ts new file mode 100644 index 00000000000..a2bfb5d0d06 --- /dev/null +++ b/workspaces/xcmetrics/plugins/xcmetrics/dev/DevXcmetricsApi.ts @@ -0,0 +1,383 @@ +/* + * 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 { DateTime } from 'luxon'; +import { + Build, + BuildCount, + BuildError, + BuildFilters, + BuildHost, + BuildMetadata, + BuildResponse, + BuildStatus, + BuildStatusResult, + BuildTime, + BuildWarning, + PaginationResult, + Target, + XcmetricsApi, + Xcode, +} from '../src/api'; + +/** + * A self-contained, in-memory implementation of the {@link XcmetricsApi} used + * to run the plugin locally without a real XCMetrics backend. The data is + * generated deterministically so that charts and tables stay stable between + * reloads while still looking realistic. + */ + +// Deterministic pseudo-random generator (mulberry32) so the dev data does not +// jump around on every render/reload. +const createRandom = (seed: number) => { + let state = seed; + return () => { + state |= 0; + state = (state + 0x6d2b79f5) | 0; + let t = Math.imul(state ^ (state >>> 15), 1 | state); + t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; + return ((t ^ (t >>> 14)) >>> 0) / 4294967296; + }; +}; + +const rand = createRandom(20210101); + +const pick = (items: readonly T[]): T => + items[Math.floor(rand() * items.length)]; + +const between = (min: number, max: number): number => + min + rand() * (max - min); + +const intBetween = (min: number, max: number): number => + Math.floor(between(min, max + 1)); + +const PROJECTS = ['iOS-App', 'PaymentsKit', 'DesignSystem', 'AnalyticsSDK']; +const SCHEMAS = ['Debug', 'Release', 'Beta', 'Staging']; +const USERS = ['alex', 'jordan', 'sam', 'taylor', 'casey']; +const MACHINES = [ + 'Alex-MacBookPro', + 'CI-Runner-01', + 'CI-Runner-02', + 'Jordan-MacStudio', +]; +const CATEGORIES = ['noop', 'incremental', 'clean']; +const STATUS_WEIGHTS: BuildStatus[] = [ + 'succeeded', + 'succeeded', + 'succeeded', + 'succeeded', + 'succeeded', + 'succeeded', + 'succeeded', + 'failed', + 'failed', + 'stopped', +]; + +const TOTAL_BUILDS = 220; +const HISTORY_DAYS = 60; + +const pad = (value: number): string => value.toString().padStart(4, '0'); + +const makeId = (prefix: string, index: number): string => + `${prefix}_${pad(index)}-A1B2-C3D4-E5F6-${pad(index)}XCMETRICS`; + +const createBuild = (index: number): Build => { + const daysAgo = Math.floor((index / TOTAL_BUILDS) * HISTORY_DAYS); + const start = DateTime.now() + .minus({ days: daysAgo }) + .set({ hour: intBetween(8, 19), minute: intBetween(0, 59), second: 0 }); + + const buildStatus = pick(STATUS_WEIGHTS); + const duration = Math.round(between(25, 540) * 100) / 100; + const compilationDuration = + Math.round(duration * between(0.55, 0.9) * 100) / 100; + const end = start.plus({ seconds: duration }); + const compilationEnd = start.plus({ seconds: compilationDuration }); + + return { + id: makeId('B', index), + userid: pick(USERS), + userid256: makeId('U', index), + projectName: pick(PROJECTS), + schema: pick(SCHEMAS), + machineName: pick(MACHINES), + category: pick(CATEGORIES), + tag: '', + isCi: rand() > 0.5, + wasSuspended: rand() > 0.95, + buildStatus, + errorCount: buildStatus === 'failed' ? intBetween(1, 6) : 0, + warningCount: intBetween(0, 14), + compiledCount: intBetween(12, 480), + duration, + compilationDuration, + day: start.toISODate()!, + startTimestamp: start.toISO({ suppressMilliseconds: true })!, + endTimestamp: end.toISO({ suppressMilliseconds: true })!, + compilationEndTimestamp: compilationEnd.toISO({ + suppressMilliseconds: true, + })!, + startTimestampMicroseconds: start.toSeconds(), + endTimestampMicroseconds: end.toSeconds(), + compilationEndTimestampMicroseconds: compilationEnd.toSeconds(), + }; +}; + +// Builds, newest first. +const BUILDS: Build[] = Array.from({ length: TOTAL_BUILDS }, (_, index) => + createBuild(index), +); + +const createTargets = (build: Build): Target[] => { + const targetNames = [ + 'Networking', + 'Models', + 'DesignSystem', + 'Analytics', + 'Persistence', + 'Feature-Login', + 'Feature-Checkout', + 'App', + ]; + const count = intBetween(5, targetNames.length); + let cursor = build.startTimestampMicroseconds; + + return Array.from({ length: count }, (_, index) => { + const targetDuration = between(0.5, build.duration / count); + const compileDuration = targetDuration * between(0.4, 0.85); + const startMicro = cursor; + const endMicro = startMicro + targetDuration; + const compilationEndMicro = startMicro + compileDuration; + cursor = endMicro; + + return { + id: `${build.id}_T${pad(index)}`, + name: targetNames[index], + category: pick(CATEGORIES), + buildIdentifier: build.id, + fetchedFromCache: rand() > 0.85, + errorCount: index === count - 1 ? build.errorCount : 0, + warningCount: intBetween(0, 4), + compiledCount: intBetween(1, 60), + duration: targetDuration, + compilationDuration: compileDuration, + day: build.day, + startTimestamp: DateTime.fromSeconds(startMicro).toISO({ + suppressMilliseconds: true, + })!, + endTimestamp: DateTime.fromSeconds(endMicro).toISO({ + suppressMilliseconds: true, + })!, + compilationEndTimestamp: DateTime.fromSeconds(compilationEndMicro).toISO({ + suppressMilliseconds: true, + })!, + startTimestampMicroseconds: startMicro, + endTimestampMicroseconds: endMicro, + compilationEndTimestampMicroseconds: compilationEndMicro, + }; + }); +}; + +const createXcode = (build: Build): Xcode => ({ + id: `${build.id}_XCODE`, + buildIdentifier: build.id, + buildNumber: '15A240d', + version: '1520', + day: build.day, +}); + +const createErrors = (build: Build): BuildError[] => + Array.from({ length: build.errorCount }, (_, index) => ({ + id: `${build.id}_E${pad(index)}`, + buildIdentifier: build.id, + parentIdentifier: `${build.id}_T0007`, + parentType: 'step', + type: 'clangError', + title: "Use of undeclared identifier 'paymentToken'", + detail: `/Users/${build.userid}/${build.projectName}/Sources/Checkout.swift:142:18: error: use of undeclared identifier 'paymentToken'\n return paymentToken\n ^~~~~~~~~~~~\n`, + severity: 2, + startingLine: 142, + endingLine: 142, + startingColumn: 18, + endingColumn: 30, + characterRangeStart: 0, + characterRangeEnd: 4096, + documentURL: `file:///Users/${build.userid}/${build.projectName}/Sources/Checkout.swift`, + day: build.day, + })); + +const createWarnings = (build: Build): BuildWarning[] => + Array.from({ length: Math.min(build.warningCount, 6) }, (_, index) => ({ + id: `${build.id}_W${pad(index)}`, + buildIdentifier: build.id, + parentIdentifier: `${build.id}_T000${index % 8}`, + parentType: 'step', + type: 'deprecatedWarning', + title: + "'UIApplication.keyWindow' is deprecated: first deprecated in iOS 13.0", + detail: null, + clangFlag: '[-Wdeprecated-declarations]', + severity: 1, + startingLine: 58 + index, + endingLine: 58 + index, + startingColumn: 22, + endingColumn: 44, + characterRangeStart: 0, + characterRangeEnd: 2048, + documentURL: `file:///Users/${build.userid}/${build.projectName}/Sources/AppDelegate.swift`, + day: build.day, + })); + +const createHost = (build: Build): BuildHost => ({ + id: `${build.id}_HOST`, + buildIdentifier: build.id, + hostOs: 'macOS', + hostOsFamily: 'Darwin', + hostOsVersion: '14.4.1', + hostArchitecture: 'arm64', + hostModel: 'Mac14,12', + cpuModel: 'Apple M2 Pro', + cpuCount: 12, + cpuSpeedGhz: 3.5, + memoryTotalMb: 32768, + memoryFreeMb: Math.round(between(2048, 16384)), + swapTotalMb: 8192, + swapFreeMb: Math.round(between(512, 8192)), + uptimeSeconds: intBetween(3600, 1_500_000), + isVirtual: build.machineName.startsWith('CI'), + timezone: 'CET', + day: build.day, +}); + +const createMetadata = (build: Build): BuildMetadata => ({ + ciProvider: build.isCi ? 'GitHubActions' : 'local', + branch: build.isCi ? 'main' : `feature/${build.userid}-work`, + commit: build.id.slice(2, 12).toLowerCase(), + pullRequest: build.isCi ? `#${intBetween(100, 999)}` : 'n/a', + xcodeVersion: '15.2', +}); + +const filterByDate = (builds: Build[], filters: BuildFilters): Build[] => { + const from = DateTime.fromISO(filters.from).startOf('day'); + const to = DateTime.fromISO(filters.to).endOf('day'); + + return builds.filter(build => { + const day = DateTime.fromISO(build.startTimestamp); + if (day < from || day > to) return false; + if (filters.buildStatus && build.buildStatus !== filters.buildStatus) { + return false; + } + if (filters.project && build.projectName !== filters.project) { + return false; + } + return true; + }); +}; + +/** A fully functional, in-memory XCMetrics API for local development. */ +export class DevXcmetricsApi implements XcmetricsApi { + async getBuild(id: string): Promise { + const build = BUILDS.find(b => b.id === id) ?? BUILDS[0]; + return { + build, + targets: createTargets(build), + xcode: createXcode(build), + }; + } + + async getBuilds(limit: number = 10): Promise { + return BUILDS.slice(0, limit); + } + + async getFilteredBuilds( + filters: BuildFilters, + page: number = 1, + perPage: number = 10, + ): Promise> { + const filtered = filterByDate(BUILDS, filters); + const start = (page - 1) * perPage; + + return { + items: filtered.slice(start, start + perPage), + metadata: { + per: perPage, + page, + total: filtered.length, + }, + }; + } + + async getBuildErrors(buildId: string): Promise { + const build = BUILDS.find(b => b.id === buildId); + return build ? createErrors(build) : []; + } + + async getBuildCounts(days: number): Promise { + return Array.from({ length: days }, (_, index) => { + const builds = intBetween(8, 60); + return { + day: DateTime.now() + .minus({ days: days - index - 1 }) + .toISODate()!, + builds, + errors: intBetween(0, Math.ceil(builds / 4)), + }; + }); + } + + async getBuildHost(buildId: string): Promise { + const build = BUILDS.find(b => b.id === buildId) ?? BUILDS[0]; + return createHost(build); + } + + async getBuildMetadata(buildId: string): Promise { + const build = BUILDS.find(b => b.id === buildId) ?? BUILDS[0]; + return createMetadata(build); + } + + async getBuildTimes(days: number): Promise { + return Array.from({ length: days }, (_, index) => { + const durationP50 = Math.round(between(45, 160) * 100) / 100; + const durationP95 = + Math.round(durationP50 * between(1.4, 2.6) * 100) / 100; + return { + day: DateTime.now() + .minus({ days: days - index - 1 }) + .toISODate()!, + durationP50, + durationP95, + totalDuration: Math.round(durationP50 * intBetween(8, 60) * 100) / 100, + }; + }); + } + + async getBuildStatuses(limit: number): Promise { + return BUILDS.slice(0, limit).map(({ id, buildStatus }) => ({ + id, + buildStatus, + })); + } + + async getBuildWarnings(buildId: string): Promise { + const build = BUILDS.find(b => b.id === buildId); + return build ? createWarnings(build) : []; + } + + async getProjects(): Promise { + return [...PROJECTS]; + } +} diff --git a/workspaces/xcmetrics/plugins/xcmetrics/dev/index.tsx b/workspaces/xcmetrics/plugins/xcmetrics/dev/index.tsx index 96fe087376a..c9666a7aae2 100644 --- a/workspaces/xcmetrics/plugins/xcmetrics/dev/index.tsx +++ b/workspaces/xcmetrics/plugins/xcmetrics/dev/index.tsx @@ -14,10 +14,22 @@ * limitations under the License. */ import { createDevApp } from '@backstage/dev-utils'; +import { createApiFactory } from '@backstage/core-plugin-api'; +import { xcmetricsApiRef } from '../src/api'; import { xcmetricsPlugin, XcmetricsPage } from '../src/plugin'; +import { DevXcmetricsApi } from './DevXcmetricsApi'; createDevApp() .registerPlugin(xcmetricsPlugin) + // Override the real HTTP client with an in-memory mock so the plugin is + // fully functional locally without a running XCMetrics backend. + .registerApi( + createApiFactory({ + api: xcmetricsApiRef, + deps: {}, + factory: () => new DevXcmetricsApi(), + }), + ) .addPage({ element: , title: 'Root Page', diff --git a/workspaces/xcmetrics/plugins/xcmetrics/package.json b/workspaces/xcmetrics/plugins/xcmetrics/package.json index 5ef02e30b50..24065f4a361 100644 --- a/workspaces/xcmetrics/plugins/xcmetrics/package.json +++ b/workspaces/xcmetrics/plugins/xcmetrics/package.json @@ -10,9 +10,7 @@ ] }, "publishConfig": { - "access": "public", - "main": "dist/index.esm.js", - "types": "dist/index.d.ts" + "access": "public" }, "homepage": "https://backstage.io", "repository": { @@ -22,6 +20,21 @@ }, "license": "Apache-2.0", "sideEffects": false, + "exports": { + ".": "./src/index.ts", + "./alpha": "./src/alpha.tsx", + "./package.json": "./package.json" + }, + "typesVersions": { + "*": { + "alpha": [ + "src/alpha.tsx" + ], + "package.json": [ + "package.json" + ] + } + }, "main": "src/index.ts", "types": "src/index.ts", "files": [ @@ -40,6 +53,7 @@ "@backstage/core-components": "backstage:^", "@backstage/core-plugin-api": "backstage:^", "@backstage/errors": "backstage:^", + "@backstage/frontend-plugin-api": "backstage:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", diff --git a/workspaces/xcmetrics/plugins/xcmetrics/report-alpha.api.md b/workspaces/xcmetrics/plugins/xcmetrics/report-alpha.api.md new file mode 100644 index 00000000000..d81c94f9a01 --- /dev/null +++ b/workspaces/xcmetrics/plugins/xcmetrics/report-alpha.api.md @@ -0,0 +1,140 @@ +## API Report File for "@backstage-community/plugin-xcmetrics" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { AnyApiFactory } from '@backstage/frontend-plugin-api'; +import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; +import { ApiFactory } from '@backstage/frontend-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; +import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionInput } from '@backstage/frontend-plugin-api'; +import { IconElement } from '@backstage/frontend-plugin-api'; +import { JSX as JSX_2 } from 'react'; +import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api'; +import { RouteRef } from '@backstage/core-plugin-api'; +import { RouteRef as RouteRef_2 } from '@backstage/frontend-plugin-api'; + +// @public (undocumented) +const _default: OverridableFrontendPlugin< + { + root: RouteRef; + }, + {}, + { + 'api:xcmetrics/xcmetrics-api': OverridableExtensionDefinition<{ + kind: 'api'; + name: 'xcmetrics-api'; + config: {}; + configInput: {}; + output: ExtensionDataRef; + inputs: {}; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; + }>; + 'page:xcmetrics': OverridableExtensionDefinition<{ + kind: 'page'; + name: undefined; + config: { + path: string | undefined; + title: string | undefined; + }; + configInput: { + path?: string | undefined; + title?: string | undefined; + }; + output: + | ExtensionDataRef + | ExtensionDataRef + | ExtensionDataRef< + RouteRef_2, + 'core.routing.ref', + { + optional: true; + } + > + | ExtensionDataRef< + string, + 'core.title', + { + optional: true; + } + > + | ExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >; + inputs: { + pages: ExtensionInput< + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef_2, + 'core.routing.ref', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'core.title', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + IconElement, + 'core.icon', + { + optional: true; + } + >, + { + singleton: false; + optional: false; + internal: false; + } + >; + }; + params: { + path: string; + title?: string; + icon?: IconElement; + loader?: () => Promise; + routeRef?: RouteRef_2; + noHeader?: boolean; + }; + }>; + } +>; +export default _default; + +// @alpha (undocumented) +export const xCRMetricsApiExtension: OverridableExtensionDefinition<{ + kind: 'api'; + name: 'xcmetrics-api'; + config: {}; + configInput: {}; + output: ExtensionDataRef; + inputs: {}; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; +}>; + +// (No @packageDocumentation comment for this package) +``` diff --git a/workspaces/xcmetrics/plugins/xcmetrics/src/alpha.tsx b/workspaces/xcmetrics/plugins/xcmetrics/src/alpha.tsx new file mode 100644 index 00000000000..9d6785c6cd1 --- /dev/null +++ b/workspaces/xcmetrics/plugins/xcmetrics/src/alpha.tsx @@ -0,0 +1,59 @@ +/* + * Copyright 2026 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 { + createFrontendPlugin, + PageBlueprint, + ApiBlueprint, + discoveryApiRef, + fetchApiRef, +} from '@backstage/frontend-plugin-api'; +import { rootRouteRef } from './routes'; +import { xcmetricsApiRef, XcmetricsClient } from './api'; + +const xCMetricsPage = PageBlueprint.make({ + params: { + path: '/xcmetrics', + routeRef: rootRouteRef, + loader: () => + import('./components/XcmetricsLayout').then(m => ), + }, +}); + +/** + * @alpha + */ +export const xCRMetricsApiExtension = ApiBlueprint.make({ + name: 'xcmetrics-api', + params: defineParams => + defineParams({ + api: xcmetricsApiRef, + deps: { + discoveryApi: discoveryApiRef, + fetchApi: fetchApiRef, + }, + factory({ discoveryApi, fetchApi }) { + return new XcmetricsClient({ discoveryApi, fetchApi }); + }, + }), +}); + +export default createFrontendPlugin({ + pluginId: 'xcmetrics', + extensions: [xCMetricsPage, xCRMetricsApiExtension], + routes: { + root: rootRouteRef, + }, +}); diff --git a/workspaces/xcmetrics/yarn.lock b/workspaces/xcmetrics/yarn.lock index c69fb01d5bf..e18e748ca59 100644 --- a/workspaces/xcmetrics/yarn.lock +++ b/workspaces/xcmetrics/yarn.lock @@ -725,6 +725,7 @@ __metadata: "@backstage/core-plugin-api": "backstage:^" "@backstage/dev-utils": "backstage:^" "@backstage/errors": "backstage:^" + "@backstage/frontend-plugin-api": "backstage:^" "@backstage/test-utils": "backstage:^" "@material-ui/core": "npm:^4.12.2" "@material-ui/icons": "npm:^4.9.1" @@ -837,7 +838,7 @@ __metadata: languageName: node linkType: hard -"@backstage/cli-defaults@backstage:^::backstage=1.51.0&npm=0.1.2, @backstage/cli-defaults@npm:^0.1.2": +"@backstage/cli-defaults@backstage:^::backstage=1.51.2&npm=0.1.2, @backstage/cli-defaults@npm:^0.1.2": version: 0.1.2 resolution: "@backstage/cli-defaults@npm:0.1.2" dependencies: @@ -1190,7 +1191,7 @@ __metadata: languageName: node linkType: hard -"@backstage/cli@backstage:^::backstage=1.51.0&npm=0.36.2, @backstage/cli@npm:^0.36.2": +"@backstage/cli@backstage:^::backstage=1.51.2&npm=0.36.2, @backstage/cli@npm:^0.36.2": version: 0.36.2 resolution: "@backstage/cli@npm:0.36.2" dependencies: @@ -1335,7 +1336,7 @@ __metadata: languageName: node linkType: hard -"@backstage/core-components@backstage:^::backstage=1.51.0&npm=0.18.10, @backstage/core-components@npm:^0.18.10": +"@backstage/core-components@backstage:^::backstage=1.51.2&npm=0.18.10, @backstage/core-components@npm:^0.18.10": version: 0.18.10 resolution: "@backstage/core-components@npm:0.18.10" dependencies: @@ -1393,7 +1394,7 @@ __metadata: languageName: node linkType: hard -"@backstage/core-plugin-api@backstage:^::backstage=1.51.0&npm=1.12.6, @backstage/core-plugin-api@npm:^1.12.6": +"@backstage/core-plugin-api@backstage:^::backstage=1.51.2&npm=1.12.6, @backstage/core-plugin-api@npm:^1.12.6": version: 1.12.6 resolution: "@backstage/core-plugin-api@npm:1.12.6" dependencies: @@ -1416,7 +1417,7 @@ __metadata: languageName: node linkType: hard -"@backstage/dev-utils@backstage:^::backstage=1.51.0&npm=1.1.23, @backstage/dev-utils@npm:^1.1.23": +"@backstage/dev-utils@backstage:^::backstage=1.51.2&npm=1.1.23, @backstage/dev-utils@npm:^1.1.23": version: 1.1.23 resolution: "@backstage/dev-utils@npm:1.1.23" dependencies: @@ -1444,7 +1445,7 @@ __metadata: languageName: node linkType: hard -"@backstage/e2e-test-utils@backstage:^::backstage=1.51.0&npm=0.1.2, @backstage/e2e-test-utils@npm:^0.1.2": +"@backstage/e2e-test-utils@backstage:^::backstage=1.51.2&npm=0.1.2, @backstage/e2e-test-utils@npm:^0.1.2": version: 0.1.2 resolution: "@backstage/e2e-test-utils@npm:0.1.2" dependencies: @@ -1459,7 +1460,7 @@ __metadata: languageName: node linkType: hard -"@backstage/errors@backstage:^::backstage=1.51.0&npm=1.3.1, @backstage/errors@npm:^1.3.1": +"@backstage/errors@backstage:^::backstage=1.51.2&npm=1.3.1, @backstage/errors@npm:^1.3.1": version: 1.3.1 resolution: "@backstage/errors@npm:1.3.1" dependencies: @@ -1492,7 +1493,7 @@ __metadata: languageName: node linkType: hard -"@backstage/frontend-plugin-api@npm:^0.17.0": +"@backstage/frontend-plugin-api@backstage:^::backstage=1.51.2&npm=0.17.1, @backstage/frontend-plugin-api@npm:^0.17.0, @backstage/frontend-plugin-api@npm:^0.17.1": version: 0.17.1 resolution: "@backstage/frontend-plugin-api@npm:0.17.1" dependencies: @@ -1748,7 +1749,7 @@ __metadata: languageName: node linkType: hard -"@backstage/repo-tools@backstage:^::backstage=1.51.0&npm=0.17.2, @backstage/repo-tools@npm:^0.17.2": +"@backstage/repo-tools@backstage:^::backstage=1.51.2&npm=0.17.2, @backstage/repo-tools@npm:^0.17.2": version: 0.17.2 resolution: "@backstage/repo-tools@npm:0.17.2" dependencies: @@ -1815,7 +1816,7 @@ __metadata: languageName: node linkType: hard -"@backstage/test-utils@backstage:^::backstage=1.51.0&npm=1.7.18, @backstage/test-utils@npm:^1.7.18": +"@backstage/test-utils@backstage:^::backstage=1.51.2&npm=1.7.18, @backstage/test-utils@npm:^1.7.18": version: 1.7.18 resolution: "@backstage/test-utils@npm:1.7.18" dependencies: