diff --git a/.changeset/breezy-badgers-train.md b/.changeset/breezy-badgers-train.md new file mode 100644 index 0000000000..8f747b8602 --- /dev/null +++ b/.changeset/breezy-badgers-train.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli-node': patch +--- + +Upgraded @yarnpkg/parsers to stable 3.0 diff --git a/.changeset/lovely-hats-pay.md b/.changeset/lovely-hats-pay.md new file mode 100644 index 0000000000..117f50e5db --- /dev/null +++ b/.changeset/lovely-hats-pay.md @@ -0,0 +1,5 @@ +--- +'@backstage/theme': patch +--- + +Internal refactor to fix an issue where the MUI 5 `v5-` class prefixing gets removed by tree shaking. diff --git a/.changeset/wild-doors-cheat.md b/.changeset/wild-doors-cheat.md new file mode 100644 index 0000000000..c302dde467 --- /dev/null +++ b/.changeset/wild-doors-cheat.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Fix `versions:check --fix` when `yarn.lock` has multiple joint versions in the same section diff --git a/docs/deployment/docker.md b/docs/deployment/docker.md index 9ec5460a88..f16a2ad13e 100644 --- a/docs/deployment/docker.md +++ b/docs/deployment/docker.md @@ -141,8 +141,12 @@ browser at `http://localhost:7007` ## Multi-stage Build -> NOTE: The `.dockerignore` is different in this setup, read on for more -> details. +:::note Note + +The `.dockerignore` is different in this setup, read on for more +details. + +::: This section describes how to set up a multi-stage Docker build that builds the entire project within Docker. This is typically slower than a host build, but is @@ -293,10 +297,14 @@ browser at `http://localhost:7007` ## Separate Frontend -> NOTE: This is an optional step, and you will lose out on the features of the -> `@backstage/plugin-app-backend` plugin. Most notably the frontend configuration -> will no longer be injected by the backend, you will instead need to use the -> correct configuration when building the frontend bundle. +:::note Note + +This is an optional step, and you will lose out on the features of the +`@backstage/plugin-app-backend` plugin. Most notably the frontend configuration +will no longer be injected by the backend, you will instead need to use the +correct configuration when building the frontend bundle. + +::: It is sometimes desirable to serve the frontend separately from the backend, either from a separate image or for example a static file serving provider. The diff --git a/docs/deployment/index.md b/docs/deployment/index.md index da2c29ba23..0524e71db8 100644 --- a/docs/deployment/index.md +++ b/docs/deployment/index.md @@ -13,8 +13,12 @@ This documentation shows common examples that may be useful when deploying Backstage for the first time, or for those without established deployment practices. -> Note: The _easiest_ way to explore Backstage is to visit the -> [live demo site](https://demo.backstage.io). +:::note Note + +The _easiest_ way to explore Backstage is to visit the +[live demo site](https://demo.backstage.io). + +::: At Spotify, we deploy software generally by: diff --git a/docs/deployment/k8s.md b/docs/deployment/k8s.md index 00bf7464e3..46ba273e14 100644 --- a/docs/deployment/k8s.md +++ b/docs/deployment/k8s.md @@ -107,10 +107,14 @@ $ echo -n "backstage" | base64 YmFja3N0YWdl ``` -> Note: Secrets are base64-encoded, but not encrypted. Be sure to enable -> [Encryption at Rest](https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/) -> for the cluster. For storing secrets in Git, consider -> [SealedSecrets or other solutions](https://learnk8s.io/kubernetes-secrets-in-git). +:::note Note + +Secrets are base64-encoded, but not encrypted. Be sure to enable +[Encryption at Rest](https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/) +for the cluster. For storing secrets in Git, consider +[SealedSecrets or other solutions](https://learnk8s.io/kubernetes-secrets-in-git). + +::: The secrets can now be applied to the Kubernetes cluster: diff --git a/docs/overview/roadmap.md b/docs/overview/roadmap.md index 186619d1df..1a41eedd63 100644 --- a/docs/overview/roadmap.md +++ b/docs/overview/roadmap.md @@ -6,91 +6,87 @@ description: Roadmap of Backstage ## The Backstage Roadmap -Backstage is currently under rapid development. This page details the project's -public roadmap, the result of ongoing collaboration between the core maintainers -and the broader Backstage community. +Backstage is still under rapid development, and this page details the project's +public roadmap. This not a complete list of all work happening in and around the +project, it only highlights the highest priority initiatives worked on by the +core maintainers. -The Backstage roadmap lays out both [“what's next”](#whats-next) and ["future work"](#future-work). With "next" we mean features planned for release within -the ongoing quarter from July through September 2022. With "future" we mean -features on the radar, but not yet scheduled. +## 2024 Fall Roadmap -| [What's next](#whats-next) | [Future work](#future-work) | -| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | -| [Backend Services (MVP)](#backend-services-mvp)
[Backstage Security Audit](#backstage-security-audit)
[Backstage Threat Model](#backstage-threat-model)
[Software Catalog pagination](#software-catalog-pagination)
[More SIGs](#more-sigs) | Ease of onboarding
Composable Homepage 1.0
Creator experience
GraphQL
Telemetry | +The initiatives listed below are planned for release within the next half-year, starting in May 2024. The roadmap is updated every 6 months, and the next update is planned for November 2024. -The long-term roadmap (12 - 36 months) is not detailed in the public roadmap. -Third-party contributions are also not currently included in the roadmap. Let us -know about any ongoing developments and we're happy to include them here as -well. +### Backend System 1.0 -## What's next +The goal of this initiative is the stable 1.0 release of the [new backend system](../backend-system/index.md). +This includes ensuring that all documentation is up to date, and includes API +reviews and refactoring efforts to ensure that what is released is both stable +and evolvable. You can follow along with this work in the [meta issue](https://github.com/backstage/backstage/issues/24493). -The feature set below is planned for the ongoing quarter, and grouped by theme. -The list order doesn't necessarily reflect priority, and the development/release -cycle will vary based on maintainer schedules. +As part of this initiative, there will also be an exploration on how to +simplify extension of backend services. It is not currently possible to augment +backend services through declarative integration, they are instead only +customizable through complete replacement. This also limits the ability to +modularize services and scale ownership of the implementations. The goal is to +provide a more flexible and scalable way to extend backend services. -### Backend Services (MVP) +### New Frontend System - Ready for Adoption -To better scale and maintain the Backstage instances, a backend services system -is planned to be introduced as part of the software architecture. This layer of -backend services will help in decoupling the various modules (e.g. Catalog and -Scaffolder) from the frontend experience. +The [new fronted system](../frontend-system/index.md) still needs more work, and +the next milestone is to improve it to the point where there is enough +confidence in the design to start encouraging adoption in the community. You can +follow along with this work in the [meta issue](https://github.com/backstage/backstage/issues/19545). +This milestone also includes reaching and executing [rollout phase 2](https://github.com/backstage/backstage/issues/19545#issuecomment-1766069146). -After the experimentation and design happened in the past quarter, soon we plan to release a first version to start providing the first benefits to adopters and developers. +Once the initial milestone is reached, the goal is to also build out broader +support for the new frontend system in the core plugins. ### Backstage Security Audit -This is the continuation of the initiative started in the previous quarters. This -quarter will see the publication of the report describing the outcome of the -audit, together the first fixes and the development of some of the changes -required to address the vulnerabilities. +This is the second security audit of the Backstage project. It is done together, +and with the support of the [Cloud Native Computing Foundation (CNCF)](https://www.cncf.io/). +This time the audit will in particular focus on the recently introduced +[authentication system](https://github.com/backstage/backstage/tree/master/beps/0003-auth-architecture-evolution), +but also cover other parts of the project. -This initiative is the first of a broader Security Strategy for Backstage. The -purpose of the Security Audit is to involve third-party companies in auditing -the platform. The benefit for the adopters is clear: we want Backstage to be as -secure as possible, and we want to make it reliable through a specific -initiative. +### Plugin Metadata -This initiative is done together with, and with the support of, the [Cloud Native Computing Foundation (CNCF)](https://www.cncf.io/). +The goal of this initiative is to provide better machine readable metadata for +Backstage packages, available both at runtime, at build-time and as part of +package registries. We want to surface information such as what packages make up +a particular plugin, what features it provides, and more generally laying the +foundation for an evolvable plugin metadata system. -### Backstage Threat Model +### MUI v5 Green-light -This is another (relevant) initiative planned to make Backstage a secure product for the adopters. The goals of this initiative are: +Material-UI v4 is still the officially supported version of MUI in Backstage. +While we have heard that adopters have had success using MUI 5, this is still an +untested path with known bugs. The goal of this initiative is to iron out any +remaining issues or gaps, and then provide a green light for migration to MUI 5. -1. Understand where security investment and attention is needed. -2. Guide the upcoming security audit. -3. Communicate expectations to Backstage adopters and inform and attract security researchers. +### Configuration Improvements -The planned artifacts are: +This initiative aims to improve the configuration experience and reliability in +Backstage. Areas for improvement include the way that configuration schema is +loaded, the way that plugins access configuration that is not owned by them, how +plugins read configuration, and how configuration visibility is handled. -- Concise high level threat model that will be included as part of the Backstage security documentation. -- Granular threat model created in conjunction with the security audit to inform further security investment areas for Backstage. +### Versioned Documentation -### Software Catalog pagination +The goal of this initiative is to provide versioned documentation at +[backstage.io](https://backstage.io). This lets us provide documentation that is +both up-to-date while at the same time not ahead of the latest release. -Today adopters with a big catalog (with several thousands of software components) might not have an ideal end-user experience when viewing the `/catalog` page. The issue is related to how the entities are fetched by the frontend. In order to provide a better end-user experience the pagination of the catalog’s entities needs to be enforced. Some experimentation is already completed but in this quarter we plan to continue, and hopefully complete, this relevant enhancement. +### Rework Pull Request & Issue Process -### More SIGs +Our current review and issue triage process is centered around either core- or +project area maintainers. The goal of this initiative is to make it simpler for +more members of the community to be involved and contribute to this process. -In the last quarter we launched the [Catalog SIG (Special Interest Group)](https://github.com/backstage/community/tree/main/sigs/sig-catalog) to better coordinate the increasing number of contributions to the project. We think that this is the proper path to follow to engage more with the contributors. For this reason we will launch other SIGs dedicated to the most interesting topics for the community. +### Catalog Observability -## Future work - -The following feature list doesn't represent a commitment to develop, and the -list order doesn't reflect any priority or importance, but these features are on -the maintainers' radar, with clear interest expressed by the community. - -- **Ease of onboarding:** A faster (with less development) and easier setup of - Backstage and the most relevant/adopted plugins. -- **Composable Homepage 1.0:** Driving this to 1.0 by adding some composable - components. -- **Creator experience:** Provide a better Backstage user experience through - visual guidelines and templates, especially navigation across plug-ins and - portal functionalities. -- **[GraphQL](https://graphql.org/) support:** Introduce the ability to query - Backstage backend services with a standard query language for APIs. -- **Telemetry:** To efficiently generate logging and metrics in such a way that - adopters can get insights so that Backstage can be monitored and improved. +The goal of this initiative is to provide better tools for debugging catalog +ingestion issues and to more generally reduce friction for setting up and +maintaining the software catalog. ## How to influence the roadmap diff --git a/docs/tutorials/setup-opentelemetry.md b/docs/tutorials/setup-opentelemetry.md index c49eef26a2..30dde79db8 100644 --- a/docs/tutorials/setup-opentelemetry.md +++ b/docs/tutorials/setup-opentelemetry.md @@ -65,13 +65,19 @@ You can now start your Backstage instance as usual, using `yarn dev`. ## Production Setup -In your `Dockerfile` add the `--require` flag which points to the `instrumentation.js` file +In your `Dockerfile`, copy `instrumentation.js` file into the root of the working directory. + +```Dockerfile +COPY --chown=${NOT_ROOT_USER}:${NOT_ROOT_USER} packages/backend/src/instrumentation.js ./ +``` + +And then add the `--require` flag that points to the file to the CMD array. ```Dockerfile // highlight-remove-next-line CMD ["node", "packages/backend", "--config", "app-config.yaml"] // highlight-add-next-line -CMD ["node", "--require", "./src/instrumentation.js", "packages/backend", "--config", "app-config.yaml"] +CMD ["node", "--require", "./instrumentation.js", "packages/backend", "--config", "app-config.yaml"] ``` If you need to disable/configure some OpenTelemetry feature there are lots of [environment variables](https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/) which you can tweak. diff --git a/packages/backend-common/src/database/connectors/mysql.ts b/packages/backend-common/src/database/connectors/mysql.ts index 1ad8b0b3be..2fb3545047 100644 --- a/packages/backend-common/src/database/connectors/mysql.ts +++ b/packages/backend-common/src/database/connectors/mysql.ts @@ -304,7 +304,7 @@ export class MysqlConnector implements Connector { const pluginDivisionMode = this.getPluginDivisionModeConfig(); if (pluginDivisionMode !== 'database') { throw new Error( - `The MySQL driver does not suppoert plugin division mode '${pluginDivisionMode}'`, + `The MySQL driver does not support plugin division mode '${pluginDivisionMode}'`, ); } diff --git a/packages/backend-common/src/reading/FetchUrlReader.test.ts b/packages/backend-common/src/reading/FetchUrlReader.test.ts index 7b5e4ebc05..8a0b24002e 100644 --- a/packages/backend-common/src/reading/FetchUrlReader.test.ts +++ b/packages/backend-common/src/reading/FetchUrlReader.test.ts @@ -77,7 +77,7 @@ describe('FetchUrlReader', () => { worker.use( rest.get('https://backstage.io/error', (_req, res, ctx) => { - return res(ctx.status(500), ctx.body('An internal error occured')); + return res(ctx.status(500), ctx.body('An internal error occurred')); }), ); }); diff --git a/packages/backend-dynamic-feature-service/src/manager/plugin-manager.test.ts b/packages/backend-dynamic-feature-service/src/manager/plugin-manager.test.ts index 487729687f..340991b11f 100644 --- a/packages/backend-dynamic-feature-service/src/manager/plugin-manager.test.ts +++ b/packages/backend-dynamic-feature-service/src/manager/plugin-manager.test.ts @@ -59,7 +59,7 @@ describe('backend-dynamic-feature-service', () => { name: string; packageManifest: ScannedPluginManifest; indexFile?: { - retativePath: string[]; + relativePath: string[]; content: string; }; expectedLogs?(location: URL): { @@ -83,7 +83,7 @@ describe('backend-dynamic-feature-service', () => { main: 'dist/index.cjs.js', }, indexFile: { - retativePath: ['dist', 'index.cjs.js'], + relativePath: ['dist', 'index.cjs.js'], content: 'exports.dynamicPluginInstaller={ kind: "new", install: () => [] }', }, @@ -127,7 +127,7 @@ describe('backend-dynamic-feature-service', () => { main: 'dist/index.cjs.js', }, indexFile: { - retativePath: ['dist', 'index.cjs.js'], + relativePath: ['dist', 'index.cjs.js'], content: `const alpha = { $$type: '@backstage/BackendFeature' }; exports["default"] = alpha;`, }, expectedLogs(location) { @@ -170,7 +170,7 @@ describe('backend-dynamic-feature-service', () => { main: 'dist/index.cjs.js', }, indexFile: { - retativePath: ['dist', 'index.cjs.js'], + relativePath: ['dist', 'index.cjs.js'], content: `const alpha = () => { return { $$type: '@backstage/BackendFeature' } }; alpha.$$type = '@backstage/BackendFeatureFactory'; exports["default"] = alpha;`, @@ -215,7 +215,7 @@ describe('backend-dynamic-feature-service', () => { main: 'dist/index.cjs.js', }, indexFile: { - retativePath: ['dist', 'index.cjs.js'], + relativePath: ['dist', 'index.cjs.js'], content: 'exports.dynamicPluginInstaller={ kind: "new", install: () => [] }', }, @@ -262,7 +262,7 @@ describe('backend-dynamic-feature-service', () => { return { errors: [ { - message: `an error occured while loading dynamic backend plugin 'backend-dynamic-plugin-test' from '${location}'`, + message: `an error occurred while loading dynamic backend plugin 'backend-dynamic-plugin-test' from '${location}'`, meta: { name: 'Error', message: expect.stringContaining( @@ -305,7 +305,7 @@ describe('backend-dynamic-feature-service', () => { main: 'dist/index.cjs.js', }, indexFile: { - retativePath: ['dist', 'index.cjs.js'], + relativePath: ['dist', 'index.cjs.js'], content: '', }, expectedLogs(location) { @@ -332,7 +332,7 @@ describe('backend-dynamic-feature-service', () => { main: 'dist/index.cjs.js', }, indexFile: { - retativePath: ['dist', 'index.cjs.js'], + relativePath: ['dist', 'index.cjs.js'], content: 'exports.dynamicPluginInstaller={ something: "else", unexpectedMethod() {} }', }, @@ -360,14 +360,14 @@ describe('backend-dynamic-feature-service', () => { main: 'dist/index.cjs.js', }, indexFile: { - retativePath: ['dist', 'index.cjs.js'], + relativePath: ['dist', 'index.cjs.js'], content: 'strange text with syntax error', }, expectedLogs(location) { return { errors: [ { - message: `an error occured while loading dynamic backend plugin 'backend-dynamic-plugin-test' from '${location}'`, + message: `an error occurred while loading dynamic backend plugin 'backend-dynamic-plugin-test' from '${location}'`, meta: { message: expect.stringContaining('Unexpected identifier'), name: 'SyntaxError', @@ -391,7 +391,7 @@ describe('backend-dynamic-feature-service', () => { main: 'dist/index.cjs.js', }, indexFile: { - retativePath: ['dist', 'index.cjs.js'], + relativePath: ['dist', 'index.cjs.js'], content: 'exports.dynamicPluginInstaller={ kind: "legacy", scaffolder: (env)=>[] }', }, @@ -458,7 +458,7 @@ describe('backend-dynamic-feature-service', () => { mockedFiles[ path.join( url.fileURLToPath(plugin.location), - ...tc.indexFile.retativePath, + ...tc.indexFile.relativePath, ) ] = tc.indexFile.content; } 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 edc3e20f72..95b0c6592c 100644 --- a/packages/backend-dynamic-feature-service/src/manager/plugin-manager.ts +++ b/packages/backend-dynamic-feature-service/src/manager/plugin-manager.ts @@ -188,7 +188,7 @@ export class DynamicPluginManager implements DynamicPluginProvider { }; } catch (error) { this.logger.error( - `an error occured while loading dynamic backend plugin '${plugin.manifest.name}' from '${plugin.location}'`, + `an error occurred while loading dynamic backend plugin '${plugin.manifest.name}' from '${plugin.location}'`, error, ); return undefined; diff --git a/packages/catalog-model/src/entity/policies/FieldFormatEntityPolicy.test.ts b/packages/catalog-model/src/entity/policies/FieldFormatEntityPolicy.test.ts index f32236e977..9fe041a839 100644 --- a/packages/catalog-model/src/entity/policies/FieldFormatEntityPolicy.test.ts +++ b/packages/catalog-model/src/entity/policies/FieldFormatEntityPolicy.test.ts @@ -65,11 +65,6 @@ describe('FieldFormatEntityPolicy', () => { await expect(policy.enforce(data)).rejects.toThrow(/kind/); }); - it('handles missing metadata gracefully', async () => { - delete data.medatata; - await expect(policy.enforce(data)).resolves.toBe(data); - }); - it('handles missing spec gracefully', async () => { delete data.spec; await expect(policy.enforce(data)).resolves.toBe(data); diff --git a/packages/cli-node/package.json b/packages/cli-node/package.json index 3bd9a98f60..a786d71949 100644 --- a/packages/cli-node/package.json +++ b/packages/cli-node/package.json @@ -35,7 +35,7 @@ "@backstage/errors": "workspace:^", "@backstage/types": "workspace:^", "@manypkg/get-packages": "^1.1.3", - "@yarnpkg/parsers": "^3.0.0-rc.4", + "@yarnpkg/parsers": "^3.0.0", "fs-extra": "^11.2.0", "semver": "^7.5.3", "zod": "^3.22.4" diff --git a/packages/cli/package.json b/packages/cli/package.json index f3093445ab..67492919d0 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -79,7 +79,7 @@ "@typescript-eslint/eslint-plugin": "^6.12.0", "@typescript-eslint/parser": "^6.7.2", "@yarnpkg/lockfile": "^1.1.0", - "@yarnpkg/parsers": "^3.0.0-rc.4", + "@yarnpkg/parsers": "^3.0.0", "bfj": "^8.0.0", "buffer": "^6.0.3", "chalk": "^4.0.0", diff --git a/packages/cli/src/lib/versioning/Lockfile.ts b/packages/cli/src/lib/versioning/Lockfile.ts index 50d0a77cfd..6af115a4da 100644 --- a/packages/cli/src/lib/versioning/Lockfile.ts +++ b/packages/cli/src/lib/versioning/Lockfile.ts @@ -98,6 +98,24 @@ export class Lockfile { return Lockfile.parse(lockfileContents); } + static #getRangesFromDataKey(key: string): string[] { + const [, name, ranges] = key.match(ENTRY_PATTERN) ?? []; + if (!name) { + throw new Error(`Failed to parse yarn.lock entry '${key}'`); + } + + return ranges.split(/\s*,\s*/).map(rangePart => { + let range = rangePart; + if (range.startsWith(`${name}@`)) { + range = range.slice(`${name}@`.length); + } + if (range.startsWith('npm:')) { + range = range.slice('npm:'.length); + } + return range; + }); + } + static parse(content: string) { const legacy = LEGACY_REGEX.test(content); @@ -113,7 +131,7 @@ export class Lockfile { for (const [key, value] of Object.entries(data)) { if (SPECIAL_OBJECT_KEYS.includes(key)) continue; - const [, name, ranges] = ENTRY_PATTERN.exec(key) ?? []; + const [, name] = ENTRY_PATTERN.exec(key) ?? []; if (!name) { throw new Error(`Failed to parse yarn.lock entry '${key}'`); } @@ -123,13 +141,8 @@ export class Lockfile { queries = []; packages.set(name, queries); } - for (let range of ranges.split(/\s*,\s*/)) { - if (range.startsWith(`${name}@`)) { - range = range.slice(`${name}@`.length); - } - if (range.startsWith('npm:')) { - range = range.slice('npm:'.length); - } + const ranges = Lockfile.#getRangesFromDataKey(key); + for (const range of ranges) { queries.push({ range, version: value.version, dataKey: key }); } } @@ -276,9 +289,34 @@ export class Lockfile { } remove(name: string, range: string): boolean { - const query = `${name}@${range}`; + const simpleQuery = `${name}@${range}`; + const query = this.getEntryOf(name, range); + const existed = Boolean(this.data[query]); - delete this.data[query]; + + if (simpleQuery === query) { + // Single-versioned entry, just delete + delete this.data[query]; + } else { + // Remove this version from the entry key. This modifies the key, so the + // package queries' needs to be updated too. + const newRanges = Lockfile.#getRangesFromDataKey(query).filter( + q => q !== simpleQuery, + ); + const newQuery = newRanges.join(', '); + + // Replace the entry with a new one without this particular range + const entry = this.data[query]; + delete this.data[query]; + this.data[newQuery] = entry; + + // Fix all package queries pointing to the old query + this.packages.get(name)?.forEach(q => { + if (q.dataKey === query) { + q.dataKey = newQuery; + } + }); + } const newEntries = this.packages.get(name)?.filter(e => e.range !== range); if (newEntries) { @@ -288,19 +326,35 @@ export class Lockfile { return existed; } + getEntryOf(name: string, range: string) { + const query = this.packages.get(name)?.find(q => q.range === range); + if (!query) { + throw new Error(`No entry data for ${name}@${range}`); + } + + return query.dataKey; + } + /** Modifies the lockfile by bumping packages to the suggested versions */ replaceVersions(results: AnalyzeResultNewVersion[]) { + // When replacing versions, we might replace the same version multiple times, + // as a query may contain multiple versions. This keeps the original version, + // to ensure we don't make mistakes. + const oldVersions = Object.fromEntries( + Object.entries(this.data).map(([key, val]) => [key, val.version]), + ); + for (const { name, range, oldVersion, newVersion } of results) { - const query = `${name}@${range}`; + const query = this.getEntryOf(name, range); // Update the backing data const entryData = this.data[query]; if (!entryData) { throw new Error(`No entry data for ${query}`); } - if (entryData.version !== oldVersion) { + if (oldVersions[query] !== oldVersion) { throw new Error( - `Expected existing version data for ${query} to be ${oldVersion}, was ${entryData.version}`, + `Expected existing version data for ${query} to be ${oldVersion}, was ${oldVersions[query]}`, ); } diff --git a/packages/theme/src/unified/MuiClassNameSetup.ts b/packages/theme/src/unified/MuiClassNameSetup.ts deleted file mode 100644 index 5d7e0b26fe..0000000000 --- a/packages/theme/src/unified/MuiClassNameSetup.ts +++ /dev/null @@ -1,24 +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 { unstable_ClassNameGenerator as ClassNameGenerator } from '@mui/material/className'; - -/** - * This API is introduced in @mui/material (v5.0.5) as a replacement of deprecated createGenerateClassName & only affects v5 Material UI components from `@mui/*` - */ -ClassNameGenerator.configure(componentName => { - return `v5-${componentName}`; -}); diff --git a/packages/theme/src/unified/UnifiedThemeProvider.test.tsx b/packages/theme/src/unified/UnifiedThemeProvider.test.tsx index da4af5abab..98fc73505d 100644 --- a/packages/theme/src/unified/UnifiedThemeProvider.test.tsx +++ b/packages/theme/src/unified/UnifiedThemeProvider.test.tsx @@ -22,7 +22,6 @@ import { useTheme as useV5Theme } from '@mui/material/styles'; import { makeStyles as makeV5Styles } from '@mui/styles'; import { render, screen } from '@testing-library/react'; import React from 'react'; -import './MuiClassNameSetup'; import { UnifiedThemeProvider } from './UnifiedThemeProvider'; import { themes } from './themes'; diff --git a/packages/theme/src/unified/UnifiedThemeProvider.tsx b/packages/theme/src/unified/UnifiedThemeProvider.tsx index 35abbe57d8..465a4dda24 100644 --- a/packages/theme/src/unified/UnifiedThemeProvider.tsx +++ b/packages/theme/src/unified/UnifiedThemeProvider.tsx @@ -15,7 +15,6 @@ */ import React, { ReactNode } from 'react'; -import './MuiClassNameSetup'; import { CssBaseline } from '@material-ui/core'; import { ThemeProvider, @@ -29,6 +28,7 @@ import { Theme as Mui5Theme, } from '@mui/material/styles'; import { UnifiedTheme } from './types'; +import { unstable_ClassNameGenerator as ClassNameGenerator } from '@mui/material/className'; /** * Props for {@link UnifiedThemeProvider}. @@ -41,10 +41,19 @@ export interface UnifiedThemeProviderProps { noCssBaseline?: boolean; } +/** + * This API is introduced in @mui/material (v5.0.5) as a replacement of deprecated createGenerateClassName & only affects v5 Material UI components from `@mui/*`. + * + * This call needs to be in the same module as the `UnifiedThemeProvider` to ensure that it doesn't get removed by tree shaking + */ +ClassNameGenerator.configure(componentName => { + return `v5-${componentName}`; +}); + // Background at https://mui.com/x/migration/migration-data-grid-v4/#using-mui-core-v4-with-v5 // Rather than disabling globals and custom seed, we instead only set a production prefix that -// won't collide with Material UI 5 styles. We've already got a separate class name generator for v5 set -// up in MuiClassNameSetup.ts, so only the production JSS needs deduplication. +// won't collide with Material UI 5 styles. We've already got the separate class name generator +// for v5 set up in just above, so only the production JSS needs deduplication. const generateV4ClassName = createGenerateClassName({ productionPrefix: 'jss4-', }); diff --git a/plugins/devtools-backend/package.json b/plugins/devtools-backend/package.json index 00d8df80ae..11441eb302 100644 --- a/plugins/devtools-backend/package.json +++ b/plugins/devtools-backend/package.json @@ -41,7 +41,7 @@ "@manypkg/get-packages": "^1.1.3", "@types/express": "*", "@yarnpkg/lockfile": "^1.1.0", - "@yarnpkg/parsers": "^3.0.0-rc.4", + "@yarnpkg/parsers": "^3.0.0", "express": "^4.18.1", "express-promise-router": "^4.1.0", "fs-extra": "^11.0.0", diff --git a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.test.tsx b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.test.tsx index 06ec3b0ebd..793ffe274b 100644 --- a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.test.tsx +++ b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.test.tsx @@ -36,19 +36,6 @@ import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { Observable } from '@backstage/types'; -// Mock needed because jsdom doesn't correctly implement box-sizing -// https://github.com/ShinyChang/React-Text-Truncate/issues/70 -// https://stackoverflow.com/questions/71916701/how-to-mock-a-react-function-component-that-takes-a-ref-prop -jest.mock('react-text-truncate', () => { - const { forwardRef } = jest.requireActual('react'); - return { - __esModule: true, - default: forwardRef((props: any, ref: any) => ( -
{props.text}
- )), - }; -}); - const mockedStarredEntitiesApi: Partial = { starredEntitie$: () => { return { diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx index 72a912e16e..f2b949f768 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx @@ -120,19 +120,6 @@ const getEntitiesMock = ( } as GetEntitiesResponse); }; -// Mock needed because jsdom doesn't correctly implement box-sizing -// https://github.com/ShinyChang/React-Text-Truncate/issues/70 -// https://stackoverflow.com/questions/71916701/how-to-mock-a-react-function-component-that-takes-a-ref-prop -jest.mock('react-text-truncate', () => { - const { forwardRef } = jest.requireActual('react'); - return { - __esModule: true, - default: forwardRef((props: any, ref: any) => ( -
{props.text}
- )), - }; -}); - describe('OwnershipCard', () => { const groupEntity: GroupEntity = { apiVersion: 'backstage.io/v1alpha1', diff --git a/plugins/techdocs/src/search/components/TechDocsSearchResultListItem.test.tsx b/plugins/techdocs/src/search/components/TechDocsSearchResultListItem.test.tsx index 15444fca9e..9ba88a78ff 100644 --- a/plugins/techdocs/src/search/components/TechDocsSearchResultListItem.test.tsx +++ b/plugins/techdocs/src/search/components/TechDocsSearchResultListItem.test.tsx @@ -18,11 +18,6 @@ import React from 'react'; import { TechDocsSearchResultListItem } from './TechDocsSearchResultListItem'; import { renderInTestApp } from '@backstage/test-utils'; -// Using canvas to render text.. -jest.mock('react-text-truncate', () => { - return ({ text }: { text: string }) => {text}; -}); - const validResult = { location: 'https://backstage.io/docs', title: 'Documentation', diff --git a/yarn.lock b/yarn.lock index 728bac0c68..60a33729d6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3622,7 +3622,7 @@ __metadata: "@backstage/errors": "workspace:^" "@backstage/types": "workspace:^" "@manypkg/get-packages": ^1.1.3 - "@yarnpkg/parsers": ^3.0.0-rc.4 + "@yarnpkg/parsers": ^3.0.0 fs-extra: ^11.2.0 semver: ^7.5.3 zod: ^3.22.4 @@ -3696,7 +3696,7 @@ __metadata: "@typescript-eslint/parser": ^6.7.2 "@vitejs/plugin-react": ^4.0.4 "@yarnpkg/lockfile": ^1.1.0 - "@yarnpkg/parsers": ^3.0.0-rc.4 + "@yarnpkg/parsers": ^3.0.0 bfj: ^8.0.0 buffer: ^6.0.3 chalk: ^4.0.0 @@ -5604,7 +5604,7 @@ __metadata: "@types/supertest": ^2.0.8 "@types/yarnpkg__lockfile": ^1.1.4 "@yarnpkg/lockfile": ^1.1.0 - "@yarnpkg/parsers": ^3.0.0-rc.4 + "@yarnpkg/parsers": ^3.0.0 express: ^4.18.1 express-promise-router: ^4.1.0 fs-extra: ^11.0.0 @@ -19136,13 +19136,13 @@ __metadata: languageName: node linkType: hard -"@yarnpkg/parsers@npm:^3.0.0-rc.4": - version: 3.0.0-rc.21 - resolution: "@yarnpkg/parsers@npm:3.0.0-rc.21" +"@yarnpkg/parsers@npm:^3.0.0": + version: 3.0.0 + resolution: "@yarnpkg/parsers@npm:3.0.0" dependencies: js-yaml: ^3.10.0 tslib: ^2.4.0 - checksum: c0741ef01089a7d452dfb75b8c24a82ddcc3e218dac22b794a06f2e50c7070378c460f63f63fb7ed0f62f634114e448e3d85a75ccd4db84e1526242ae9b4a7d5 + checksum: fefe5ecafb5bfa2b678ac9ba9259810fdda40142afd9d0b7e0e5cc1cce1fd824dffc52217c5e429807481d8fd18ead074bd317e64fd626335d3c9f1a320bade2 languageName: node linkType: hard