From 5a42d712790f05f4c7b34cb2674e73b48c97e715 Mon Sep 17 00:00:00 2001 From: Sarah Medeiros Date: Thu, 15 Dec 2022 15:55:42 -0500 Subject: [PATCH 01/94] Fix tag picker and add test Signed-off-by: Sarah Medeiros --- .../EntityTagPicker/EntityTagPicker.test.tsx | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/plugins/catalog-react/src/components/EntityTagPicker/EntityTagPicker.test.tsx b/plugins/catalog-react/src/components/EntityTagPicker/EntityTagPicker.test.tsx index 7da357d806..de7bbb89be 100644 --- a/plugins/catalog-react/src/components/EntityTagPicker/EntityTagPicker.test.tsx +++ b/plugins/catalog-react/src/components/EntityTagPicker/EntityTagPicker.test.tsx @@ -202,4 +202,33 @@ describe('', () => { tags: new EntityTagFilter(['tag2']), }); }); + it('removes tags from filters if there are none available', async () => { + const updateFilters = jest.fn(); + const mockCatalogApiRefNoTags = { + getEntityFacets: async () => ({ + facets: { + 'metadata.tags': {}, + }, + }), + } as unknown as CatalogApi; + + render( + + + + + , + , + ); + await waitFor(() => + expect(updateFilters).toHaveBeenLastCalledWith({ + tags: undefined, + }), + ); + }); }); From 37426f6f5ef206325d1165cacee665240ccbcaf1 Mon Sep 17 00:00:00 2001 From: Sarah Medeiros Date: Thu, 15 Dec 2022 15:57:00 -0500 Subject: [PATCH 02/94] Add changeset Signed-off-by: Sarah Medeiros --- .changeset/funny-pianos-grow.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/funny-pianos-grow.md diff --git a/.changeset/funny-pianos-grow.md b/.changeset/funny-pianos-grow.md new file mode 100644 index 0000000000..b08b5f0768 --- /dev/null +++ b/.changeset/funny-pianos-grow.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Fix filter bug that leaves filters selected when they are not available for the selected kind. From 228f4db4fa541a4a070f8c805ac624f8ca7807d6 Mon Sep 17 00:00:00 2001 From: Sarah Medeiros Date: Thu, 15 Dec 2022 15:57:57 -0500 Subject: [PATCH 03/94] Fix tag filter bug Signed-off-by: Sarah Medeiros --- .../src/components/EntityTagPicker/EntityTagPicker.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/plugins/catalog-react/src/components/EntityTagPicker/EntityTagPicker.tsx b/plugins/catalog-react/src/components/EntityTagPicker/EntityTagPicker.tsx index 25f02e8adc..1b7b68cbde 100644 --- a/plugins/catalog-react/src/components/EntityTagPicker/EntityTagPicker.tsx +++ b/plugins/catalog-react/src/components/EntityTagPicker/EntityTagPicker.tsx @@ -94,9 +94,12 @@ export const EntityTagPicker = (props: EntityTagPickerProps) => { useEffect(() => { updateFilters({ - tags: selectedTags.length ? new EntityTagFilter(selectedTags) : undefined, + tags: + selectedTags.length && Object.keys(availableTags ?? {}).length + ? new EntityTagFilter(selectedTags) + : undefined, }); - }, [selectedTags, updateFilters]); + }, [selectedTags, updateFilters, availableTags]); if (!Object.keys(availableTags ?? {}).length) return null; From a5789f2977e5b6bf3d6001e35145aeb690593afd Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 16 Dec 2022 14:28:47 +0100 Subject: [PATCH 04/94] microsite-next: Add Sidebar, make references compatible Signed-off-by: Johan Haals --- microsite-next/package.json | 8 +- microsite-next/scripts/pre-build.js | 49 ++++ microsite-next/sidebars.json | 367 +++++++++++++++++++++++++++- microsite-next/yarn.lock | 128 +++++++++- 4 files changed, 545 insertions(+), 7 deletions(-) create mode 100644 microsite-next/scripts/pre-build.js diff --git a/microsite-next/package.json b/microsite-next/package.json index 1b33d2896f..478fce0196 100644 --- a/microsite-next/package.json +++ b/microsite-next/package.json @@ -4,9 +4,8 @@ "license": "Apache-2.0", "private": true, "scripts": { - "examples": "docusaurus-examples", - "start": "docusaurus start", - "build": "docusaurus build", + "start": "node scripts/pre-build.js && docusaurus start", + "build": "node scripts/pre-build.js && docusaurus build", "prettier:check": "prettier --check .", "publish-gh-pages": "docusaurus-publish", "write-translations": "docusaurus-write-translations", @@ -20,7 +19,8 @@ "devDependencies": { "@spotify/prettier-config": "^14.0.0", "js-yaml": "^4.1.0", - "prettier": "^2.6.2" + "prettier": "^2.6.2", + "replace": "^1.2.2" }, "prettier": "@spotify/prettier-config", "dependencies": { diff --git a/microsite-next/scripts/pre-build.js b/microsite-next/scripts/pre-build.js new file mode 100644 index 0000000000..1c4f6e6787 --- /dev/null +++ b/microsite-next/scripts/pre-build.js @@ -0,0 +1,49 @@ +/* + * 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. + */ + +const replace = require('replace'); +const { existsSync, writeFileSync, mkdirSync } = require('fs'); +const path = require('path'); + +const PLACEHOLDER = `--- +id: "index" +title: "Package Index" +description: "Index of all Backstage Packages" +--- +Run \`yarn build:api-docs\` to generate the API docs. +`; + +async function main() { + const referencesDir = '../docs/reference'; + if (existsSync(referencesDir)) { + console.log('Removing HTML comments from docs/reference folder'); + await replace({ + regex: '', + replacement: '', + paths: [referencesDir], + recursive: true, + silent: false, + }); + } else { + mkdirSync(referencesDir); + writeFileSync(path.join(referencesDir, 'index.md'), PLACEHOLDER); + } +} + +main().catch(error => { + console.error(error.stack); + process.exit(1); +}); diff --git a/microsite-next/sidebars.json b/microsite-next/sidebars.json index 0967ef424b..67c4209976 100644 --- a/microsite-next/sidebars.json +++ b/microsite-next/sidebars.json @@ -1 +1,366 @@ -{} +{ + "releases": { + "Release Notes": [ + "releases/v1.8.0", + "releases/v1.7.0", + "releases/v1.6.0", + "releases/v1.5.0", + "releases/v1.4.0", + "releases/v1.3.0", + "releases/v1.2.0", + "releases/v1.1.0", + "releases/v1.0.0" + ] + }, + "docs": { + "Overview": [ + "overview/what-is-backstage", + "overview/architecture-overview", + "overview/roadmap", + "overview/vision", + "overview/background", + "overview/adopting", + "overview/versioning-policy", + "overview/threat-model", + "overview/support", + "overview/glossary", + "overview/logos" + ], + "Getting Started": [ + "getting-started/index", + "getting-started/configuration", + "getting-started/create-an-app", + "getting-started/running-backstage-locally", + { + "type": "category", + "label": "App configuration", + "items": [ + "getting-started/configure-app-with-plugins", + "getting-started/app-custom-theme", + "getting-started/homepage" + ] + }, + "getting-started/keeping-backstage-updated", + "getting-started/concepts", + "getting-started/contributors", + "getting-started/project-structure" + ], + "Local Development": [ + { + "type": "category", + "label": "CLI", + "items": [ + "local-dev/cli-overview", + "local-dev/cli-build-system", + "local-dev/cli-commands" + ] + }, + "local-dev/linking-local-packages" + ], + "Core Features": [ + { + "type": "category", + "label": "Software Catalog", + "items": [ + "features/software-catalog/software-catalog-overview", + "features/software-catalog/life-of-an-entity", + "features/software-catalog/configuration", + "features/software-catalog/system-model", + "features/software-catalog/descriptor-format", + "features/software-catalog/references", + "features/software-catalog/well-known-annotations", + "features/software-catalog/well-known-relations", + "features/software-catalog/well-known-statuses", + "features/software-catalog/extending-the-model", + "features/software-catalog/external-integrations", + "features/software-catalog/catalog-customization", + "features/software-catalog/software-catalog-api" + ] + }, + { + "type": "category", + "label": "Kubernetes", + "items": [ + "features/kubernetes/overview", + "features/kubernetes/installation", + "features/kubernetes/configuration", + "features/kubernetes/authentication", + "features/kubernetes/troubleshooting" + ] + }, + { + "type": "category", + "label": "Software Templates", + "items": [ + "features/software-templates/software-templates-index", + "features/software-templates/configuration", + "features/software-templates/adding-templates", + "features/software-templates/writing-templates", + "features/software-templates/input-examples", + "features/software-templates/builtin-actions", + "features/software-templates/writing-custom-actions", + "features/software-templates/writing-custom-field-extensions", + "features/software-templates/writing-custom-step-layouts", + "features/software-templates/migrating-from-v1beta2-to-v1beta3" + ] + }, + { + "type": "category", + "label": "Backstage Search", + "items": [ + "features/search/search-overview", + "features/search/getting-started", + "features/search/concepts", + "features/search/architecture", + "features/search/search-engines", + "features/search/how-to-guides" + ] + }, + { + "type": "category", + "label": "TechDocs", + "items": [ + "features/techdocs/techdocs-overview", + "features/techdocs/getting-started", + "features/techdocs/concepts", + "features/techdocs/addons", + "features/techdocs/architecture", + "features/techdocs/creating-and-publishing", + "features/techdocs/configuration", + "features/techdocs/using-cloud-storage", + "features/techdocs/configuring-ci-cd", + "features/techdocs/cli", + "features/techdocs/how-to-guides", + "features/techdocs/troubleshooting", + "features/techdocs/faqs" + ] + } + ], + "Integrations": [ + "integrations/index", + { + "type": "category", + "label": "AWS S3", + "items": [ + "integrations/aws-s3/locations", + "integrations/aws-s3/discovery" + ] + }, + { + "type": "category", + "label": "Azure", + "items": [ + "integrations/azure/locations", + "integrations/azure/discovery", + "integrations/azure/org" + ] + }, + { + "type": "category", + "label": "Bitbucket Cloud", + "items": [ + "integrations/bitbucketCloud/locations", + "integrations/bitbucketCloud/discovery" + ] + }, + { + "type": "category", + "label": "Bitbucket Server", + "items": [ + "integrations/bitbucketServer/locations", + "integrations/bitbucketServer/discovery" + ] + }, + { + "type": "category", + "label": "Datadog", + "items": ["integrations/datadog-rum/installation"] + }, + { + "type": "category", + "label": "Gerrit", + "items": [ + "integrations/gerrit/locations", + "integrations/gerrit/discovery" + ] + }, + { + "type": "category", + "label": "GitHub", + "items": [ + "integrations/github/locations", + "integrations/github/discovery", + "integrations/github/org", + "integrations/github/github-apps" + ] + }, + { + "type": "category", + "label": "GitLab", + "items": [ + "integrations/gitlab/locations", + "integrations/gitlab/discovery" + ] + }, + { + "type": "category", + "label": "Gitea", + "items": ["integrations/gitea/locations"] + }, + { + "type": "category", + "label": "Google GCS", + "items": ["integrations/google-cloud-storage/locations"] + }, + { + "type": "category", + "label": "LDAP", + "items": ["integrations/ldap/org"] + } + ], + "Plugins": [ + "plugins/index", + "plugins/existing-plugins", + "plugins/create-a-plugin", + "plugins/plugin-development", + "plugins/structure-of-a-plugin", + "plugins/integrating-plugin-into-software-catalog", + "plugins/integrating-search-into-plugins", + "plugins/composability", + "plugins/customization", + "plugins/analytics", + "plugins/feature-flags", + { + "type": "category", + "label": "Backends and APIs", + "items": [ + "plugins/proxying", + "plugins/backend-plugin", + "plugins/call-existing-api", + "plugins/url-reader", + "plugins/new-backend-system" + ] + }, + { + "type": "category", + "label": "Testing", + "items": ["plugins/testing"] + }, + { + "type": "category", + "label": "Publishing", + "items": [ + "plugins/publish-private", + "plugins/add-to-marketplace", + "plugins/observability" + ] + } + ], + "Configuration": [ + "conf/index", + "conf/reading", + "conf/writing", + "conf/defining" + ], + "Auth and identity": [ + "auth/index", + { + "type": "category", + "label": "Included providers", + "items": [ + "auth/auth0/provider", + "auth/atlassian/provider", + "auth/bitbucket/provider", + "auth/microsoft/provider", + "auth/github/provider", + "auth/gitlab/provider", + "auth/google/provider", + "auth/google/gcp-iap-auth", + "auth/okta/provider", + "auth/onelogin/provider", + "auth/oauth2-proxy/provider" + ] + }, + "auth/identity-resolver", + "auth/oauth", + "auth/oidc", + "auth/add-auth-provider", + "auth/service-to-service-auth", + "auth/troubleshooting", + "auth/glossary" + ], + "Permissions": [ + "permissions/overview", + "permissions/concepts", + "permissions/getting-started", + "permissions/writing-a-policy", + "permissions/frontend-integration", + "permissions/custom-rules", + { + "type": "category", + "label": "Tutorial: using Permissions in your plugin", + "items": [ + "permissions/plugin-authors/01-setup", + "permissions/plugin-authors/02-adding-a-basic-permission-check", + "permissions/plugin-authors/03-adding-a-resource-permission-check", + "permissions/plugin-authors/04-authorizing-access-to-paginated-data", + "permissions/plugin-authors/05-frontend-authorization" + ] + } + ], + "Deployment": [ + "deployment/index", + "deployment/scaling", + "deployment/docker", + "deployment/k8s", + "deployment/heroku" + ], + "Designing for Backstage": [ + "dls/design", + "dls/component-design-guidelines", + "dls/contributing-to-storybook", + "dls/figma" + ], + "API Reference": [ + { + "type": "category", + "label": "Guides", + "items": ["api/utility-apis"] + }, + { + "type": "category", + "label": "API Reference", + "items": ["reference/index"] + }, + "api/deprecations" + ], + "Tutorials": [ + "tutorials/journey", + "tutorials/quickstart-app-plugin", + "tutorials/react-router-stable-migration", + "tutorials/package-role-migration", + "tutorials/migrating-away-from-core", + "tutorials/configuring-plugin-databases", + "tutorials/switching-sqlite-postgres", + "tutorials/using-backstage-proxy-within-plugin", + "tutorials/yarn-migration" + ], + "Architecture Decision Records (ADRs)": [ + "architecture-decisions/adrs-overview", + "architecture-decisions/adrs-adr001", + "architecture-decisions/adrs-adr002", + "architecture-decisions/adrs-adr003", + "architecture-decisions/adrs-adr004", + "architecture-decisions/adrs-adr005", + "architecture-decisions/adrs-adr006", + "architecture-decisions/adrs-adr007", + "architecture-decisions/adrs-adr008", + "architecture-decisions/adrs-adr009", + "architecture-decisions/adrs-adr010", + "architecture-decisions/adrs-adr011", + "architecture-decisions/adrs-adr012", + "architecture-decisions/adrs-adr013" + ], + "FAQ": ["FAQ"] + } +} diff --git a/microsite-next/yarn.lock b/microsite-next/yarn.lock index fa35b93945..c7dcd41c28 100644 --- a/microsite-next/yarn.lock +++ b/microsite-next/yarn.lock @@ -3573,6 +3573,7 @@ __metadata: prettier: ^2.6.2 react: ^17.0.2 react-dom: ^17.0.2 + replace: ^1.2.2 languageName: unknown linkType: soft @@ -3827,6 +3828,13 @@ __metadata: languageName: node linkType: hard +"camelcase@npm:^5.0.0": + version: 5.3.1 + resolution: "camelcase@npm:5.3.1" + checksum: e6effce26b9404e3c0f301498184f243811c30dfe6d0b9051863bd8e4034d09c8c2923794f280d6827e5aa055f6c434115ff97864a16a963366fb35fd673024b + languageName: node + linkType: hard + "camelcase@npm:^6.2.0": version: 6.3.0 resolution: "camelcase@npm:6.3.0" @@ -3860,7 +3868,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^2.0.0": +"chalk@npm:2.4.2, chalk@npm:^2.0.0": version: 2.4.2 resolution: "chalk@npm:2.4.2" dependencies: @@ -4021,6 +4029,17 @@ __metadata: languageName: node linkType: hard +"cliui@npm:^6.0.0": + version: 6.0.0 + resolution: "cliui@npm:6.0.0" + dependencies: + string-width: ^4.2.0 + strip-ansi: ^6.0.0 + wrap-ansi: ^6.2.0 + checksum: 4fcfd26d292c9f00238117f39fc797608292ae36bac2168cfee4c85923817d0607fe21b3329a8621e01aedf512c99b7eaa60e363a671ffd378df6649fb48ae42 + languageName: node + linkType: hard + "clone-deep@npm:^4.0.1": version: 4.0.1 resolution: "clone-deep@npm:4.0.1" @@ -4597,6 +4616,13 @@ __metadata: languageName: node linkType: hard +"decamelize@npm:^1.2.0": + version: 1.2.0 + resolution: "decamelize@npm:1.2.0" + checksum: ad8c51a7e7e0720c70ec2eeb1163b66da03e7616d7b98c9ef43cce2416395e84c1e9548dd94f5f6ffecfee9f8b94251fc57121a8b021f2ff2469b2bae247b8aa + languageName: node + linkType: hard + "decompress-response@npm:^3.3.0": version: 3.3.0 resolution: "decompress-response@npm:3.3.0" @@ -5355,7 +5381,7 @@ __metadata: languageName: node linkType: hard -"find-up@npm:^4.0.0": +"find-up@npm:^4.0.0, find-up@npm:^4.1.0": version: 4.1.0 resolution: "find-up@npm:4.1.0" dependencies: @@ -5544,6 +5570,13 @@ __metadata: languageName: node linkType: hard +"get-caller-file@npm:^2.0.1": + version: 2.0.5 + resolution: "get-caller-file@npm:2.0.5" + checksum: b9769a836d2a98c3ee734a88ba712e62703f1df31b94b784762c433c27a386dd6029ff55c2a920c392e33657d80191edbf18c61487e198844844516f843496b9 + languageName: node + linkType: hard + "get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.1": version: 1.1.3 resolution: "get-intrinsic@npm:1.1.3" @@ -7164,6 +7197,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:3.0.5": + version: 3.0.5 + resolution: "minimatch@npm:3.0.5" + dependencies: + brace-expansion: ^1.1.7 + checksum: a3b84b426eafca947741b864502cee02860c4e7b145de11ad98775cfcf3066fef422583bc0ffce0952ddf4750c1ccf4220b1556430d4ce10139f66247d87d69e + languageName: node + linkType: hard + "minimatch@npm:3.1.2, minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1": version: 3.1.2 resolution: "minimatch@npm:3.1.2" @@ -8982,6 +9024,27 @@ __metadata: languageName: node linkType: hard +"replace@npm:^1.2.2": + version: 1.2.2 + resolution: "replace@npm:1.2.2" + dependencies: + chalk: 2.4.2 + minimatch: 3.0.5 + yargs: ^15.3.1 + bin: + replace: bin/replace.js + search: bin/search.js + checksum: 1d69f43937a5fdf9dea278e78d6f3b51c1889ba5135bd201918bbda6330684adf8276e8e90e1c021034420dd4df239e51c23ca40752cb9bc6180c153d6d46a37 + languageName: node + linkType: hard + +"require-directory@npm:^2.1.1": + version: 2.1.1 + resolution: "require-directory@npm:2.1.1" + checksum: fb47e70bf0001fdeabdc0429d431863e9475e7e43ea5f94ad86503d918423c1543361cc5166d713eaa7029dd7a3d34775af04764bebff99ef413111a5af18c80 + languageName: node + linkType: hard + "require-from-string@npm:^2.0.2": version: 2.0.2 resolution: "require-from-string@npm:2.0.2" @@ -8996,6 +9059,13 @@ __metadata: languageName: node linkType: hard +"require-main-filename@npm:^2.0.0": + version: 2.0.0 + resolution: "require-main-filename@npm:2.0.0" + checksum: e9e294695fea08b076457e9ddff854e81bffbe248ed34c1eec348b7abbd22a0d02e8d75506559e2265e96978f3c4720bd77a6dad84755de8162b357eb6c778c7 + languageName: node + linkType: hard + "requires-port@npm:^1.0.0": version: 1.0.0 resolution: "requires-port@npm:1.0.0" @@ -10623,6 +10693,13 @@ __metadata: languageName: node linkType: hard +"which-module@npm:^2.0.0": + version: 2.0.0 + resolution: "which-module@npm:2.0.0" + checksum: 809f7fd3dfcb2cdbe0180b60d68100c88785084f8f9492b0998c051d7a8efe56784492609d3f09ac161635b78ea29219eb1418a98c15ce87d085bce905705c9c + languageName: node + linkType: hard + "which@npm:^1.3.1": version: 1.3.1 resolution: "which@npm:1.3.1" @@ -10679,6 +10756,17 @@ __metadata: languageName: node linkType: hard +"wrap-ansi@npm:^6.2.0": + version: 6.2.0 + resolution: "wrap-ansi@npm:6.2.0" + dependencies: + ansi-styles: ^4.0.0 + string-width: ^4.1.0 + strip-ansi: ^6.0.0 + checksum: 6cd96a410161ff617b63581a08376f0cb9162375adeb7956e10c8cd397821f7eb2a6de24eb22a0b28401300bf228c86e50617cd568209b5f6775b93c97d2fe3a + languageName: node + linkType: hard + "wrap-ansi@npm:^7.0.0": version: 7.0.0 resolution: "wrap-ansi@npm:7.0.0" @@ -10775,6 +10863,13 @@ __metadata: languageName: node linkType: hard +"y18n@npm:^4.0.0": + version: 4.0.3 + resolution: "y18n@npm:4.0.3" + checksum: 014dfcd9b5f4105c3bb397c1c8c6429a9df004aa560964fb36732bfb999bfe83d45ae40aeda5b55d21b1ee53d8291580a32a756a443e064317953f08025b1aa4 + languageName: node + linkType: hard + "yallist@npm:^4.0.0": version: 4.0.0 resolution: "yallist@npm:4.0.0" @@ -10789,6 +10884,35 @@ __metadata: languageName: node linkType: hard +"yargs-parser@npm:^18.1.2": + version: 18.1.3 + resolution: "yargs-parser@npm:18.1.3" + dependencies: + camelcase: ^5.0.0 + decamelize: ^1.2.0 + checksum: 60e8c7d1b85814594d3719300ecad4e6ae3796748b0926137bfec1f3042581b8646d67e83c6fc80a692ef08b8390f21ddcacb9464476c39bbdf52e34961dd4d9 + languageName: node + linkType: hard + +"yargs@npm:^15.3.1": + version: 15.4.1 + resolution: "yargs@npm:15.4.1" + dependencies: + cliui: ^6.0.0 + decamelize: ^1.2.0 + find-up: ^4.1.0 + get-caller-file: ^2.0.1 + require-directory: ^2.1.1 + require-main-filename: ^2.0.0 + set-blocking: ^2.0.0 + string-width: ^4.2.0 + which-module: ^2.0.0 + y18n: ^4.0.0 + yargs-parser: ^18.1.2 + checksum: 40b974f508d8aed28598087720e086ecd32a5fd3e945e95ea4457da04ee9bdb8bdd17fd91acff36dc5b7f0595a735929c514c40c402416bbb87c03f6fb782373 + languageName: node + linkType: hard + "yocto-queue@npm:^0.1.0": version: 0.1.0 resolution: "yocto-queue@npm:0.1.0" From 7a7a53138b3a3a13fad970a046a3d6250350eaf5 Mon Sep 17 00:00:00 2001 From: Sarah Medeiros Date: Fri, 16 Dec 2022 09:58:18 -0500 Subject: [PATCH 05/94] Update .changeset/funny-pianos-grow.md Co-authored-by: Johan Haals Signed-off-by: Sarah Medeiros --- .changeset/funny-pianos-grow.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/funny-pianos-grow.md b/.changeset/funny-pianos-grow.md index b08b5f0768..7823ca82b0 100644 --- a/.changeset/funny-pianos-grow.md +++ b/.changeset/funny-pianos-grow.md @@ -2,4 +2,4 @@ '@backstage/plugin-catalog-react': patch --- -Fix filter bug that leaves filters selected when they are not available for the selected kind. +Fixed bug in `EntityTagPicker` that filtered on unavailable tags for the selected kind. From 2f45ef5cf275d256885c22b115809e4628776c26 Mon Sep 17 00:00:00 2001 From: Sarah Medeiros Date: Fri, 16 Dec 2022 10:34:38 -0500 Subject: [PATCH 06/94] Add changeset for pr #15058 Signed-off-by: Sarah Medeiros --- .changeset/funny-pianos-grow.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.changeset/funny-pianos-grow.md b/.changeset/funny-pianos-grow.md index 7823ca82b0..c9241cd818 100644 --- a/.changeset/funny-pianos-grow.md +++ b/.changeset/funny-pianos-grow.md @@ -3,3 +3,7 @@ --- Fixed bug in `EntityTagPicker` that filtered on unavailable tags for the selected kind. + +Fixed bug in `EntityOwnerPicker` that filtered on unavailable tags for the selected kind. + +Fixed bug in `EntityKindPicker` that filtered on unavailable tags for the selected kind. From 2bce511a1084b3088f3ce29a7111d30724a2b8c2 Mon Sep 17 00:00:00 2001 From: Sarah Medeiros Date: Fri, 16 Dec 2022 10:35:43 -0500 Subject: [PATCH 07/94] Fix wording Signed-off-by: Sarah Medeiros --- .changeset/funny-pianos-grow.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/funny-pianos-grow.md b/.changeset/funny-pianos-grow.md index c9241cd818..17f1d83927 100644 --- a/.changeset/funny-pianos-grow.md +++ b/.changeset/funny-pianos-grow.md @@ -6,4 +6,4 @@ Fixed bug in `EntityTagPicker` that filtered on unavailable tags for the selecte Fixed bug in `EntityOwnerPicker` that filtered on unavailable tags for the selected kind. -Fixed bug in `EntityKindPicker` that filtered on unavailable tags for the selected kind. +Fixed bug in `EntityLifecyclePicker` that filtered on unavailable tags for the selected kind. From 36d6ec5d86f5fff58e47bf33331a507269cfe2c6 Mon Sep 17 00:00:00 2001 From: Maximilian Vorbrodt Date: Fri, 9 Dec 2022 11:56:46 +0100 Subject: [PATCH 08/94] make diagram scale to viewport without growing on its own Signed-off-by: Maximilian Vorbrodt --- .../src/components/GroupsExplorerContent/GroupsDiagram.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/explore/src/components/GroupsExplorerContent/GroupsDiagram.tsx b/plugins/explore/src/components/GroupsExplorerContent/GroupsDiagram.tsx index 674ef1e43f..371548fb2e 100644 --- a/plugins/explore/src/components/GroupsExplorerContent/GroupsDiagram.tsx +++ b/plugins/explore/src/components/GroupsExplorerContent/GroupsDiagram.tsx @@ -43,9 +43,12 @@ import useAsync from 'react-use/lib/useAsync'; const useStyles = makeStyles((theme: BackstageTheme) => ({ graph: { + height: '100%', + position: 'absolute', minHeight: '100%', }, graphWrapper: { + position: 'relative', height: '100%', }, organizationNode: { From d318d4f659abfae2418e5297b73819e5ffd9cf00 Mon Sep 17 00:00:00 2001 From: Maximilian Vorbrodt Date: Fri, 9 Dec 2022 12:06:45 +0100 Subject: [PATCH 09/94] add changeset Signed-off-by: Maximilian Vorbrodt --- .changeset/thick-stingrays-smile.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/thick-stingrays-smile.md diff --git a/.changeset/thick-stingrays-smile.md b/.changeset/thick-stingrays-smile.md new file mode 100644 index 0000000000..2b56803ede --- /dev/null +++ b/.changeset/thick-stingrays-smile.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-explore': patch +--- + +Makes the `GroupsDiagram` not grown on screen on its own. From da89b09b0feb0a79982ff213b1a5fe09cb737de1 Mon Sep 17 00:00:00 2001 From: Maximilian Vorbrodt Date: Sun, 18 Dec 2022 10:17:48 +0100 Subject: [PATCH 10/94] makes whole graph visible when loading page and does not grow on its own Signed-off-by: Maximilian Vorbrodt --- .../src/components/GroupsExplorerContent/GroupsDiagram.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/explore/src/components/GroupsExplorerContent/GroupsDiagram.tsx b/plugins/explore/src/components/GroupsExplorerContent/GroupsDiagram.tsx index 371548fb2e..fd1d5ba5fb 100644 --- a/plugins/explore/src/components/GroupsExplorerContent/GroupsDiagram.tsx +++ b/plugins/explore/src/components/GroupsExplorerContent/GroupsDiagram.tsx @@ -43,12 +43,11 @@ import useAsync from 'react-use/lib/useAsync'; const useStyles = makeStyles((theme: BackstageTheme) => ({ graph: { - height: '100%', - position: 'absolute', minHeight: '100%', + flex: 1, }, graphWrapper: { - position: 'relative', + display: 'flex', height: '100%', }, organizationNode: { From c37b9e4c68ab7a0243d9f25bd4a2f3efb27415f6 Mon Sep 17 00:00:00 2001 From: Sarah Medeiros Date: Mon, 19 Dec 2022 10:12:29 -0500 Subject: [PATCH 11/94] Clean up ternary Signed-off-by: Sarah Medeiros --- .../src/components/EntityTagPicker/EntityTagPicker.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/catalog-react/src/components/EntityTagPicker/EntityTagPicker.tsx b/plugins/catalog-react/src/components/EntityTagPicker/EntityTagPicker.tsx index 1b7b68cbde..4a1dc03e96 100644 --- a/plugins/catalog-react/src/components/EntityTagPicker/EntityTagPicker.tsx +++ b/plugins/catalog-react/src/components/EntityTagPicker/EntityTagPicker.tsx @@ -93,9 +93,10 @@ export const EntityTagPicker = (props: EntityTagPickerProps) => { }, [queryParamTags]); useEffect(() => { + const tags = Object.keys(availableTags ?? {}); updateFilters({ tags: - selectedTags.length && Object.keys(availableTags ?? {}).length + selectedTags.length && tags.length ? new EntityTagFilter(selectedTags) : undefined, }); From ae4426be1c36e2dfc86fcc0dd15802936fff31e8 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 20 Dec 2022 15:43:27 +0100 Subject: [PATCH 12/94] chore: skip await Signed-off-by: Johan Haals --- microsite-next/scripts/pre-build.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsite-next/scripts/pre-build.js b/microsite-next/scripts/pre-build.js index 1c4f6e6787..8b4fb4f71e 100644 --- a/microsite-next/scripts/pre-build.js +++ b/microsite-next/scripts/pre-build.js @@ -30,7 +30,7 @@ async function main() { const referencesDir = '../docs/reference'; if (existsSync(referencesDir)) { console.log('Removing HTML comments from docs/reference folder'); - await replace({ + replace({ regex: '', replacement: '', paths: [referencesDir], From 47c10706dfab958feb85c7760eaf3169282c3325 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 21 Dec 2022 14:03:24 +0100 Subject: [PATCH 13/94] attempt to unbreak the duplicated versions problem in master MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/bright-pants-cry.md | 5 +++++ packages/cli/src/lib/versioning/Lockfile.ts | 11 ++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 .changeset/bright-pants-cry.md diff --git a/.changeset/bright-pants-cry.md b/.changeset/bright-pants-cry.md new file mode 100644 index 0000000000..53ba0c9521 --- /dev/null +++ b/.changeset/bright-pants-cry.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Fixing the error `Error: No existing version was accepted for range ^0.12.0, searching through 0.11.2,0.0.0-use.local, for package @backstage/core-components`, which can happen when there are multiple matching versions for a package, and one of them uses `workspace:^` as its range. diff --git a/packages/cli/src/lib/versioning/Lockfile.ts b/packages/cli/src/lib/versioning/Lockfile.ts index 016593ea8d..ab6d98521f 100644 --- a/packages/cli/src/lib/versioning/Lockfile.ts +++ b/packages/cli/src/lib/versioning/Lockfile.ts @@ -190,9 +190,14 @@ export class Lockfile { } // Find all versions currently in use - const versions = Array.from(new Set(entries.map(e => e.version))).sort( - (v1, v2) => semver.rcompare(v1, v2), - ); + const versions = Array.from(new Set(entries.map(e => e.version))) + .map(v => { + // Translate workspace:^ references to the actual version + return v === '0.0.0-use.local' + ? require(`${name}/package.json`).version + : v; + }) + .sort((v1, v2) => semver.rcompare(v1, v2)); // If we're not using at least 2 different versions we're done if (versions.length < 2) { From 9513a6fff9b0be6fd387380ba76f0d1fcaa13424 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 21 Dec 2022 10:10:15 +0100 Subject: [PATCH 14/94] chore: add the examples to the dockerfile Signed-off-by: blam --- .github/workflows/uffizzi-build.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/uffizzi-build.yml b/.github/workflows/uffizzi-build.yml index 163531a755..5d66d4b610 100644 --- a/.github/workflows/uffizzi-build.yml +++ b/.github/workflows/uffizzi-build.yml @@ -55,6 +55,11 @@ jobs: images: registry.uffizzi.com/${{ env.UUID_TAG_APP }} tags: type=raw,value=60d + - name: Add examples directory to Dockerfile + run: | + echo "COPY examples /app/examples" >> ./example-app/packages/backend/Dockerfile + cat ./example-app/packages/backend/Dockerfile + - name: Build Image uses: docker/build-push-action@v2 with: From 8e964492b0c35aa9f9f59d16a319e50a39e336b8 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 21 Dec 2022 10:30:06 +0100 Subject: [PATCH 15/94] chore: fix Signed-off-by: blam --- .github/workflows/uffizzi-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/uffizzi-build.yml b/.github/workflows/uffizzi-build.yml index 5d66d4b610..9e5c0e61df 100644 --- a/.github/workflows/uffizzi-build.yml +++ b/.github/workflows/uffizzi-build.yml @@ -57,7 +57,7 @@ jobs: - name: Add examples directory to Dockerfile run: | - echo "COPY examples /app/examples" >> ./example-app/packages/backend/Dockerfile + echo "COPY examples /examples" >> ./example-app/packages/backend/Dockerfile cat ./example-app/packages/backend/Dockerfile - name: Build Image From bf9d3fc29a47c1016a8f4cc6bbc7531b7c4abcff Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 21 Dec 2022 10:42:15 +0100 Subject: [PATCH 16/94] chore: fixing addition of the examples Signed-off-by: blam --- .github/workflows/uffizzi-build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/uffizzi-build.yml b/.github/workflows/uffizzi-build.yml index 9e5c0e61df..55d0c63145 100644 --- a/.github/workflows/uffizzi-build.yml +++ b/.github/workflows/uffizzi-build.yml @@ -58,7 +58,6 @@ jobs: - name: Add examples directory to Dockerfile run: | echo "COPY examples /examples" >> ./example-app/packages/backend/Dockerfile - cat ./example-app/packages/backend/Dockerfile - name: Build Image uses: docker/build-push-action@v2 From 78b593f9ad10e630b66f229c195640a1b21b3b05 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 21 Dec 2022 11:04:21 +0100 Subject: [PATCH 17/94] chore: use the e2e test method for building uffizi containers Signed-off-by: blam --- .github/workflows/uffizzi-build.yml | 2 +- packages/e2e-test/src/commands/index.ts | 3 ++- packages/e2e-test/src/commands/run.ts | 18 ++++++++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/.github/workflows/uffizzi-build.yml b/.github/workflows/uffizzi-build.yml index 55d0c63145..5a68ad9568 100644 --- a/.github/workflows/uffizzi-build.yml +++ b/.github/workflows/uffizzi-build.yml @@ -28,7 +28,7 @@ jobs: - name: backstage create-app | delete if already exists run: | rm -rf ./example-app - npx @backstage/create-app + node ./packages/e2e-test/.bin/e2e-test create -o ./example-app env: BACKSTAGE_APP_NAME: example-app diff --git a/packages/e2e-test/src/commands/index.ts b/packages/e2e-test/src/commands/index.ts index 95d311427c..db9ea225a3 100644 --- a/packages/e2e-test/src/commands/index.ts +++ b/packages/e2e-test/src/commands/index.ts @@ -15,8 +15,9 @@ */ import { Command } from 'commander'; -import { run } from './run'; +import { run, create } from './run'; export function registerCommands(program: Command) { program.command('run').description('Run e2e tests').action(run); + program.command('create').option('-o, --output ').action(create); } diff --git a/packages/e2e-test/src/commands/run.ts b/packages/e2e-test/src/commands/run.ts index 3eedb90b21..b022862c8c 100644 --- a/packages/e2e-test/src/commands/run.ts +++ b/packages/e2e-test/src/commands/run.ts @@ -43,6 +43,24 @@ const templatePackagePaths = [ 'packages/create-app/templates/default-app/packages/backend/package.json.hbs', ]; +export async function create({ output }: { output: string }) { + const outputDir = resolvePath(process.cwd(), output); + + const rootDir = await fs.mkdtemp(resolvePath(os.tmpdir(), 'backstage-e2e-')); + print(`CLI E2E test root: ${rootDir}\n`); + + print('Building dist workspace'); + const workspaceDir = await buildDistWorkspace('workspace', rootDir); + + // // Otherwise yarn will refuse to install with CI=true + process.env.YARN_ENABLE_IMMUTABLE_INSTALLS = 'false'; + + print('Creating a Backstage App'); + const appDir = await createApp('test-app', workspaceDir, rootDir); + + await fs.move(appDir, outputDir, { overwrite: true }); +} + export async function run() { const rootDir = await fs.mkdtemp(resolvePath(os.tmpdir(), 'backstage-e2e-')); print(`CLI E2E test root: ${rootDir}\n`); From 3357d8bc895a797dc18189e0e589644d8ed42f40 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 21 Dec 2022 11:09:59 +0100 Subject: [PATCH 18/94] chore: revert e2e changes Signed-off-by: blam --- .github/workflows/uffizzi-build.yml | 22 +++++++--------------- packages/e2e-test/src/commands/index.ts | 1 - packages/e2e-test/src/commands/run.ts | 18 ------------------ 3 files changed, 7 insertions(+), 34 deletions(-) diff --git a/.github/workflows/uffizzi-build.yml b/.github/workflows/uffizzi-build.yml index 5a68ad9568..025b604c1a 100644 --- a/.github/workflows/uffizzi-build.yml +++ b/.github/workflows/uffizzi-build.yml @@ -25,21 +25,17 @@ jobs: node-version: 16.x registry-url: https://registry.npmjs.org/ - - name: backstage create-app | delete if already exists + - name: yarn install run: | - rm -rf ./example-app - node ./packages/e2e-test/.bin/e2e-test create -o ./example-app - env: - BACKSTAGE_APP_NAME: example-app + yarn install + + - name: Use Uffizzi's backstage app config + run: | + cp -f ./backstage/.github/uffizzi/uffizzi.production.app-config.yaml ./app-config.production.yaml; - name: yarn build run: | yarn build:all - working-directory: ./example-app - - - name: Use Uffizzi's backstage app config - run: | - cp ./backstage/.github/uffizzi/uffizzi.production.app-config.yaml ./example-app/app-config.production.yaml; - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 @@ -55,15 +51,11 @@ jobs: images: registry.uffizzi.com/${{ env.UUID_TAG_APP }} tags: type=raw,value=60d - - name: Add examples directory to Dockerfile - run: | - echo "COPY examples /examples" >> ./example-app/packages/backend/Dockerfile - - name: Build Image uses: docker/build-push-action@v2 with: context: example-app - file: example-app/packages/backend/Dockerfile + file: packages/backend/Dockerfile tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} push: true diff --git a/packages/e2e-test/src/commands/index.ts b/packages/e2e-test/src/commands/index.ts index db9ea225a3..4a7cb7272b 100644 --- a/packages/e2e-test/src/commands/index.ts +++ b/packages/e2e-test/src/commands/index.ts @@ -19,5 +19,4 @@ import { run, create } from './run'; export function registerCommands(program: Command) { program.command('run').description('Run e2e tests').action(run); - program.command('create').option('-o, --output ').action(create); } diff --git a/packages/e2e-test/src/commands/run.ts b/packages/e2e-test/src/commands/run.ts index b022862c8c..3eedb90b21 100644 --- a/packages/e2e-test/src/commands/run.ts +++ b/packages/e2e-test/src/commands/run.ts @@ -43,24 +43,6 @@ const templatePackagePaths = [ 'packages/create-app/templates/default-app/packages/backend/package.json.hbs', ]; -export async function create({ output }: { output: string }) { - const outputDir = resolvePath(process.cwd(), output); - - const rootDir = await fs.mkdtemp(resolvePath(os.tmpdir(), 'backstage-e2e-')); - print(`CLI E2E test root: ${rootDir}\n`); - - print('Building dist workspace'); - const workspaceDir = await buildDistWorkspace('workspace', rootDir); - - // // Otherwise yarn will refuse to install with CI=true - process.env.YARN_ENABLE_IMMUTABLE_INSTALLS = 'false'; - - print('Creating a Backstage App'); - const appDir = await createApp('test-app', workspaceDir, rootDir); - - await fs.move(appDir, outputDir, { overwrite: true }); -} - export async function run() { const rootDir = await fs.mkdtemp(resolvePath(os.tmpdir(), 'backstage-e2e-')); print(`CLI E2E test root: ${rootDir}\n`); From df53479f34c5bae30f2235c7e68fba8bdd65ce01 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 21 Dec 2022 11:10:57 +0100 Subject: [PATCH 19/94] chore: revert Signed-off-by: blam Signed-off-by: blam --- packages/e2e-test/src/commands/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/e2e-test/src/commands/index.ts b/packages/e2e-test/src/commands/index.ts index 4a7cb7272b..95d311427c 100644 --- a/packages/e2e-test/src/commands/index.ts +++ b/packages/e2e-test/src/commands/index.ts @@ -15,7 +15,7 @@ */ import { Command } from 'commander'; -import { run, create } from './run'; +import { run } from './run'; export function registerCommands(program: Command) { program.command('run').description('Run e2e tests').action(run); From 90666d48d7e0fbcd0c7d26daa5425831dfdc2410 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 21 Dec 2022 11:13:33 +0100 Subject: [PATCH 20/94] chore: fix checkout path Signed-off-by: blam --- .github/workflows/uffizzi-build.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/uffizzi-build.yml b/.github/workflows/uffizzi-build.yml index 025b604c1a..afeecd2347 100644 --- a/.github/workflows/uffizzi-build.yml +++ b/.github/workflows/uffizzi-build.yml @@ -16,8 +16,6 @@ jobs: steps: - name: checkout uses: actions/checkout@v3 - with: - path: backstage - name: setup-node uses: actions/setup-node@v3 @@ -31,7 +29,7 @@ jobs: - name: Use Uffizzi's backstage app config run: | - cp -f ./backstage/.github/uffizzi/uffizzi.production.app-config.yaml ./app-config.production.yaml; + cp -f ./.github/uffizzi/uffizzi.production.app-config.yaml ./app-config.production.yaml; - name: yarn build run: | From c2bcf2c720856cf7e67d43b0fb41876a18ca54d1 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 21 Dec 2022 11:15:44 +0100 Subject: [PATCH 21/94] chore: install cache prefix Signed-off-by: blam --- .github/workflows/uffizzi-build.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/uffizzi-build.yml b/.github/workflows/uffizzi-build.yml index afeecd2347..7e50d2ce7b 100644 --- a/.github/workflows/uffizzi-build.yml +++ b/.github/workflows/uffizzi-build.yml @@ -24,8 +24,9 @@ jobs: registry-url: https://registry.npmjs.org/ - name: yarn install - run: | - yarn install + uses: backstage/actions/yarn-install@v0.5.12 + with: + cache-prefix: linux-v16 - name: Use Uffizzi's backstage app config run: | From 47f71e29184b62420607d945d52de20403fb09e8 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 21 Dec 2022 11:36:48 +0100 Subject: [PATCH 22/94] chore: need to run typescript Signed-off-by: blam --- .github/workflows/uffizzi-build.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/uffizzi-build.yml b/.github/workflows/uffizzi-build.yml index 7e50d2ce7b..ffa3f43b43 100644 --- a/.github/workflows/uffizzi-build.yml +++ b/.github/workflows/uffizzi-build.yml @@ -32,6 +32,10 @@ jobs: run: | cp -f ./.github/uffizzi/uffizzi.production.app-config.yaml ./app-config.production.yaml; + - name: yarn build + run: | + yarn tsc:full + - name: yarn build run: | yarn build:all From 49308859dcf49f1bf12908fbaa5557907942a40c Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 21 Dec 2022 11:45:23 +0100 Subject: [PATCH 23/94] chore: fixing max old space Signed-off-by: blam --- .github/workflows/uffizzi-build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/uffizzi-build.yml b/.github/workflows/uffizzi-build.yml index ffa3f43b43..dde9739aba 100644 --- a/.github/workflows/uffizzi-build.yml +++ b/.github/workflows/uffizzi-build.yml @@ -8,6 +8,8 @@ on: jobs: build-backstage: + env: + NODE_OPTIONS: --max-old-space-size=4096 name: Build PR image runs-on: ubuntu-latest if: ${{ github.event_name != 'pull_request' || github.event.action != 'closed' }} From 77fa4d45d5cc465830a8a360541db3b070129d21 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 21 Dec 2022 11:55:58 +0100 Subject: [PATCH 24/94] chore: fix naming and remove example-app prefix Signed-off-by: blam --- .github/workflows/uffizzi-build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/uffizzi-build.yml b/.github/workflows/uffizzi-build.yml index dde9739aba..adee4473f0 100644 --- a/.github/workflows/uffizzi-build.yml +++ b/.github/workflows/uffizzi-build.yml @@ -34,11 +34,11 @@ jobs: run: | cp -f ./.github/uffizzi/uffizzi.production.app-config.yaml ./app-config.production.yaml; - - name: yarn build + - name: typescript build run: | yarn tsc:full - - name: yarn build + - name: backstage build run: | yarn build:all @@ -59,7 +59,7 @@ jobs: - name: Build Image uses: docker/build-push-action@v2 with: - context: example-app + context: . file: packages/backend/Dockerfile tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} From 164d5640d83d787140d6becd8ca501e5df1d4b9c Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 21 Dec 2022 12:46:57 +0100 Subject: [PATCH 25/94] husky is not installed inside the docker container when running with --production installs Signed-off-by: blam --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6e99f4f176..f276d42416 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "techdocs-cli": "node scripts/techdocs-cli.js", "techdocs-cli:dev": "cross-env TECHDOCS_CLI_DEV_MODE=true node scripts/techdocs-cli.js", "prepare": "husky install", - "postinstall": "husky install" + "postinstall": "husky install || true" }, "workspaces": { "packages": [ From 23037b25fc9691a8a8f2db245b879fb2f242c013 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 21 Dec 2022 13:24:28 +0100 Subject: [PATCH 26/94] chore: fixing app config Signed-off-by: blam --- .github/uffizzi/uffizzi.production.app-config.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/uffizzi/uffizzi.production.app-config.yaml b/.github/uffizzi/uffizzi.production.app-config.yaml index d6df12a6af..0d33ec5c38 100644 --- a/.github/uffizzi/uffizzi.production.app-config.yaml +++ b/.github/uffizzi/uffizzi.production.app-config.yaml @@ -8,3 +8,7 @@ backend: origin: ${UFFIZZI_URL} methods: [GET, POST, PUT, DELETE] credentials: true + auth: + keys: + # random mock key for uffizi deployments + - secret: 5TXvdjVZFxF7qf9K5RAYRDoGrLzJooqa From e37884e87f95101193074491e99c20a8c5cf35ab Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 21 Dec 2022 13:25:01 +0100 Subject: [PATCH 27/94] chore; fixing dockerfile to include the production cofnig Signed-off-by: blam --- packages/backend/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile index eca6ce82c2..7dc11e2e12 100644 --- a/packages/backend/Dockerfile +++ b/packages/backend/Dockerfile @@ -46,4 +46,4 @@ RUN --mount=type=cache,target=/home/node/.yarn/berry/cache,sharing=locked,uid=10 COPY --chown=node:node packages/backend/dist/bundle.tar.gz app-config*.yaml ./ RUN tar xzf bundle.tar.gz && rm bundle.tar.gz -CMD ["node", "packages/backend", "--config", "app-config.yaml"] +CMD ["node", "packages/backend", "--config", "app-config.yaml", "--config", "app-config.production.yaml"] From e64ffefc5329f97cf1b18ea0aa81236387adb881 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 21 Dec 2022 14:01:06 +0100 Subject: [PATCH 28/94] chore: more config Signed-off-by: blam --- .github/uffizzi/uffizzi.production.app-config.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/uffizzi/uffizzi.production.app-config.yaml b/.github/uffizzi/uffizzi.production.app-config.yaml index 0d33ec5c38..6e62fa2e38 100644 --- a/.github/uffizzi/uffizzi.production.app-config.yaml +++ b/.github/uffizzi/uffizzi.production.app-config.yaml @@ -12,3 +12,11 @@ backend: keys: # random mock key for uffizi deployments - secret: 5TXvdjVZFxF7qf9K5RAYRDoGrLzJooqa +auth: + environment: production + providers: {} + +catalog: + locations: + - type: url + target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml From 02c1dee6a0df4f172bbb572a65281c0641cfdcce Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 21 Dec 2022 14:20:37 +0100 Subject: [PATCH 29/94] chore: merging the config is bad Signed-off-by: blam --- .../uffizzi.production.app-config.yaml | 147 +++++++++++++++++- .github/workflows/uffizzi-build.yml | 2 +- packages/backend/Dockerfile | 2 +- 3 files changed, 144 insertions(+), 7 deletions(-) diff --git a/.github/uffizzi/uffizzi.production.app-config.yaml b/.github/uffizzi/uffizzi.production.app-config.yaml index 6e62fa2e38..1a48a38eb1 100644 --- a/.github/uffizzi/uffizzi.production.app-config.yaml +++ b/.github/uffizzi/uffizzi.production.app-config.yaml @@ -4,19 +4,156 @@ app: backend: baseUrl: ${UFFIZZI_URL} - cors: - origin: ${UFFIZZI_URL} - methods: [GET, POST, PUT, DELETE] - credentials: true auth: keys: # random mock key for uffizi deployments - secret: 5TXvdjVZFxF7qf9K5RAYRDoGrLzJooqa + listen: + port: 7007 + database: + client: pg + connection: + host: ${POSTGRES_HOST} + user: ${POSTGRES_USER} + password: ${POSTGRES_PASSWORD} + port: 5432 + cache: + store: memory + cors: + origin: http://localhost:3000 + methods: [GET, HEAD, PATCH, POST, PUT, DELETE] + credentials: true + csp: + connect-src: ["'self'", 'http:', 'https:'] + auth: environment: production - providers: {} + providers: + myproxy: + production: {} catalog: locations: - type: url target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml + +techdocs: + builder: 'local' # Alternatives - 'external' + generator: + runIn: 'docker' + # dockerImage: my-org/techdocs # use a custom docker image + # pullImage: true # or false to disable automatic pulling of image (e.g. if custom docker login is required) + publisher: + type: 'local' # Alternatives - 'googleGcs' or 'awsS3' or 'azureBlobStorage' or 'openStackSwift'. Read documentation for using alternatives. + +dynatrace: + baseUrl: https://your.dynatrace.instance.com + +# Score-cards sample configuration. +scorecards: + jsonDataUrl: https://raw.githubusercontent.com/Oriflame/backstage-plugins/main/plugins/score-card/sample-data/ + wikiLinkTemplate: https://link-to-wiki/{id} + +sentry: + organization: my-company + +rollbar: + organization: my-company + # NOTE: The rollbar-backend & accountToken key may be deprecated in the future (replaced by a proxy config) + accountToken: my-rollbar-account-token + +lighthouse: + baseUrl: http://localhost:3003 + +kubernetes: + serviceLocatorMethod: + type: 'multiTenant' + clusterLocatorMethods: + - type: 'config' + clusters: [] +kafka: + clientId: backstage + clusters: + - name: cluster + dashboardUrl: https://akhq.io/ + brokers: + - localhost:9092 +allure: + baseUrl: http://localhost:5050/allure-docker-service +integrations: + github: + - host: github.com + token: ${GITHUB_TOKEN} +scaffolder: {} +costInsights: + engineerCost: 200000 + engineerThreshold: 0.5 + products: + computeEngine: + name: Compute Engine + icon: compute + cloudDataflow: + name: Cloud Dataflow + icon: data + cloudStorage: + name: Cloud Storage + icon: storage + bigQuery: + name: BigQuery + icon: search + events: + name: Events + icon: data + metrics: + DAU: + name: Daily Active Users + default: true + MSC: + name: Monthly Subscribers + currencies: + engineers: + label: 'Engineers 🛠' + unit: 'engineer' + usd: + label: 'US Dollars 💵' + kind: 'USD' + unit: 'dollar' + prefix: '$' + rate: 1 + carbonOffsetTons: + label: 'Carbon Offset Tons ♻️⚖️s' + kind: 'CARBON_OFFSET_TONS' + unit: 'carbon offset ton' + rate: 3.5 + beers: + label: 'Beers 🍺' + kind: 'BEERS' + unit: 'beer' + rate: 4.5 + pintsIceCream: + label: 'Pints of Ice Cream 🍦' + kind: 'PINTS_OF_ICE_CREAM' + unit: 'ice cream pint' + rate: 5.5 +pagerduty: + eventsBaseUrl: 'https://events.pagerduty.com/v2' +jenkins: + instances: + - name: default + baseUrl: https://jenkins.example.com + username: backstage-bot + apiKey: 123456789abcdef0123456789abcedf012 + +azureDevOps: + host: dev.azure.com + token: my-token + organization: my-company + +apacheAirflow: + baseUrl: https://your.airflow.instance.com + +gocd: + baseUrl: https://your.gocd.instance.com + +permission: + enabled: true diff --git a/.github/workflows/uffizzi-build.yml b/.github/workflows/uffizzi-build.yml index adee4473f0..54f100229a 100644 --- a/.github/workflows/uffizzi-build.yml +++ b/.github/workflows/uffizzi-build.yml @@ -32,7 +32,7 @@ jobs: - name: Use Uffizzi's backstage app config run: | - cp -f ./.github/uffizzi/uffizzi.production.app-config.yaml ./app-config.production.yaml; + cp -f ./.github/uffizzi/uffizzi.production.app-config.yaml ./app-config.yaml - name: typescript build run: | diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile index 7dc11e2e12..eca6ce82c2 100644 --- a/packages/backend/Dockerfile +++ b/packages/backend/Dockerfile @@ -46,4 +46,4 @@ RUN --mount=type=cache,target=/home/node/.yarn/berry/cache,sharing=locked,uid=10 COPY --chown=node:node packages/backend/dist/bundle.tar.gz app-config*.yaml ./ RUN tar xzf bundle.tar.gz && rm bundle.tar.gz -CMD ["node", "packages/backend", "--config", "app-config.yaml", "--config", "app-config.production.yaml"] +CMD ["node", "packages/backend", "--config", "app-config.yaml"] From 745868199eeb75d4a513a36b827c5bb1837c5b91 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 21 Dec 2022 14:24:50 +0100 Subject: [PATCH 30/94] chore: fix uffizzi link Signed-off-by: blam --- .github/uffizzi/docker-compose.uffizzi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/uffizzi/docker-compose.uffizzi.yml b/.github/uffizzi/docker-compose.uffizzi.yml index f505205505..d19979c8bb 100644 --- a/.github/uffizzi/docker-compose.uffizzi.yml +++ b/.github/uffizzi/docker-compose.uffizzi.yml @@ -21,7 +21,7 @@ services: entrypoint: '/bin/sh' command: - '-c' - - "APP_CONFIG_app_baseUrl=$$UFFIZZI_URL APP_CONFIG_backend_baseUrl=$$UFFIZZI_URL APP_CONFIG_auth_environment='production' node packages/backend --config app-config.yaml --config app-config.production.yaml" + - "APP_CONFIG_app_baseUrl=$$UFFIZZI_URL APP_CONFIG_backend_baseUrl=$$UFFIZZI_URL APP_CONFIG_auth_environment='production' node packages/backend --config app-config.yaml" db: image: postgres From b781c9458428538ef01acde248daf756f6718542 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 21 Dec 2022 14:27:12 +0100 Subject: [PATCH 31/94] chore: mock Signed-off-by: blam Signed-off-by: blam --- .github/workflows/uffizzi-build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/uffizzi-build.yml b/.github/workflows/uffizzi-build.yml index 54f100229a..25306e0f57 100644 --- a/.github/workflows/uffizzi-build.yml +++ b/.github/workflows/uffizzi-build.yml @@ -10,6 +10,7 @@ jobs: build-backstage: env: NODE_OPTIONS: --max-old-space-size=4096 + UFFIZZI_URL: https://uffizzi.com name: Build PR image runs-on: ubuntu-latest if: ${{ github.event_name != 'pull_request' || github.event.action != 'closed' }} From 033384c3b54f37cc5b41cd7bcb46031ec6a1dd28 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 21 Dec 2022 14:41:21 +0100 Subject: [PATCH 32/94] chore: fixing compose file for uffizi and postgres Signed-off-by: blam --- .github/uffizzi/docker-compose.uffizzi.yml | 2 ++ .github/uffizzi/uffizzi.production.app-config.yaml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/uffizzi/docker-compose.uffizzi.yml b/.github/uffizzi/docker-compose.uffizzi.yml index d19979c8bb..8ac3bb7ab8 100644 --- a/.github/uffizzi/docker-compose.uffizzi.yml +++ b/.github/uffizzi/docker-compose.uffizzi.yml @@ -8,6 +8,8 @@ x-uffizzi: services: backstage: image: '${BACKSTAGE_IMAGE}' + depends_on: + - db environment: POSTGRES_HOST: localhost POSTGRES_USER: postgres diff --git a/.github/uffizzi/uffizzi.production.app-config.yaml b/.github/uffizzi/uffizzi.production.app-config.yaml index 1a48a38eb1..a962060fc8 100644 --- a/.github/uffizzi/uffizzi.production.app-config.yaml +++ b/.github/uffizzi/uffizzi.production.app-config.yaml @@ -13,7 +13,7 @@ backend: database: client: pg connection: - host: ${POSTGRES_HOST} + host: db user: ${POSTGRES_USER} password: ${POSTGRES_PASSWORD} port: 5432 From 570d2be33119c06ecc83ed672326ed1c4683744f Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 21 Dec 2022 14:44:22 +0100 Subject: [PATCH 33/94] chore: remove permissions enabled Signed-off-by: blam --- .github/uffizzi/uffizzi.production.app-config.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/uffizzi/uffizzi.production.app-config.yaml b/.github/uffizzi/uffizzi.production.app-config.yaml index a962060fc8..faf78e7239 100644 --- a/.github/uffizzi/uffizzi.production.app-config.yaml +++ b/.github/uffizzi/uffizzi.production.app-config.yaml @@ -154,6 +154,3 @@ apacheAirflow: gocd: baseUrl: https://your.gocd.instance.com - -permission: - enabled: true From 7cef9c36232233e19de39d2faf4c47b252de4fef Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 21 Dec 2022 14:46:04 +0100 Subject: [PATCH 34/94] chore: fixing reading allowlist Signed-off-by: blam --- .github/uffizzi/uffizzi.production.app-config.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/uffizzi/uffizzi.production.app-config.yaml b/.github/uffizzi/uffizzi.production.app-config.yaml index faf78e7239..a7880ad4a3 100644 --- a/.github/uffizzi/uffizzi.production.app-config.yaml +++ b/.github/uffizzi/uffizzi.production.app-config.yaml @@ -25,6 +25,10 @@ backend: credentials: true csp: connect-src: ["'self'", 'http:', 'https:'] + reading: + allow: + - host: github.com + - host: raw.githubusercontent.com auth: environment: production From 4c2b469e8e276c0e0272f32a9b9b41a3b3dd070c Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 21 Dec 2022 14:46:48 +0100 Subject: [PATCH 35/94] chore: remove github token Signed-off-by: blam --- .github/uffizzi/docker-compose.uffizzi.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/uffizzi/docker-compose.uffizzi.yml b/.github/uffizzi/docker-compose.uffizzi.yml index 8ac3bb7ab8..53f3c13a2e 100644 --- a/.github/uffizzi/docker-compose.uffizzi.yml +++ b/.github/uffizzi/docker-compose.uffizzi.yml @@ -14,7 +14,6 @@ services: POSTGRES_HOST: localhost POSTGRES_USER: postgres POSTGRES_PASSWORD: example - GITHUB_TOKEN: abc NODE_ENV: production deploy: resources: From c6778236e575ca2607834be788f0489c8367a3ee Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 21 Dec 2022 14:55:09 +0100 Subject: [PATCH 36/94] chore: fixing config Signed-off-by: blam --- .github/uffizzi/uffizzi.production.app-config.yaml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/.github/uffizzi/uffizzi.production.app-config.yaml b/.github/uffizzi/uffizzi.production.app-config.yaml index a7880ad4a3..c65bc51786 100644 --- a/.github/uffizzi/uffizzi.production.app-config.yaml +++ b/.github/uffizzi/uffizzi.production.app-config.yaml @@ -25,16 +25,9 @@ backend: credentials: true csp: connect-src: ["'self'", 'http:', 'https:'] - reading: - allow: - - host: github.com - - host: raw.githubusercontent.com - auth: environment: production - providers: - myproxy: - production: {} + providers: {} catalog: locations: From 06c6bc786d6a3f2695d09f88b3497d21bbdd9592 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 21 Dec 2022 15:30:35 +0100 Subject: [PATCH 37/94] chore: remove depends_on Signed-off-by: blam --- .github/uffizzi/docker-compose.uffizzi.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/uffizzi/docker-compose.uffizzi.yml b/.github/uffizzi/docker-compose.uffizzi.yml index 53f3c13a2e..74477e06aa 100644 --- a/.github/uffizzi/docker-compose.uffizzi.yml +++ b/.github/uffizzi/docker-compose.uffizzi.yml @@ -8,8 +8,7 @@ x-uffizzi: services: backstage: image: '${BACKSTAGE_IMAGE}' - depends_on: - - db + environment: POSTGRES_HOST: localhost POSTGRES_USER: postgres From 5e7cc957e9cd546e48d7c41a27037bbd1628ad62 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 21 Dec 2022 15:31:03 +0100 Subject: [PATCH 38/94] chore: host is db Signed-off-by: blam --- .github/uffizzi/docker-compose.uffizzi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/uffizzi/docker-compose.uffizzi.yml b/.github/uffizzi/docker-compose.uffizzi.yml index 74477e06aa..49039cad2a 100644 --- a/.github/uffizzi/docker-compose.uffizzi.yml +++ b/.github/uffizzi/docker-compose.uffizzi.yml @@ -10,7 +10,7 @@ services: image: '${BACKSTAGE_IMAGE}' environment: - POSTGRES_HOST: localhost + POSTGRES_HOST: db POSTGRES_USER: postgres POSTGRES_PASSWORD: example NODE_ENV: production From a62a1f9dca0446424e806f5b4e0be0de09d7d4e1 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 21 Dec 2022 15:30:41 +0100 Subject: [PATCH 39/94] cli: apply package duplication filter during yarn start Signed-off-by: Patrik Oldsberg --- .changeset/forty-years-retire.md | 5 +++++ packages/cli/src/commands/start/startFrontend.ts | 8 ++++---- 2 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 .changeset/forty-years-retire.md diff --git a/.changeset/forty-years-retire.md b/.changeset/forty-years-retire.md new file mode 100644 index 0000000000..e7b887a1e4 --- /dev/null +++ b/.changeset/forty-years-retire.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +The frontend serve task now filters out allowed package duplicates during its package check, just like `versions:bump` and `versions:check`. diff --git a/packages/cli/src/commands/start/startFrontend.ts b/packages/cli/src/commands/start/startFrontend.ts index f6923632c8..689fb28466 100644 --- a/packages/cli/src/commands/start/startFrontend.ts +++ b/packages/cli/src/commands/start/startFrontend.ts @@ -21,7 +21,7 @@ import { serveBundle } from '../../lib/bundler'; import { loadCliConfig } from '../../lib/config'; import { paths } from '../../lib/paths'; import { Lockfile } from '../../lib/versioning'; -import { includedFilter } from '../versions/lint'; +import { forbiddenDuplicatesFilter, includedFilter } from '../versions/lint'; interface StartAppOptions { verifyVersions?: boolean; @@ -37,9 +37,9 @@ export async function startFrontend(options: StartAppOptions) { const result = lockfile.analyze({ filter: includedFilter, }); - const problemPackages = [...result.newVersions, ...result.newRanges].map( - ({ name }) => name, - ); + const problemPackages = [...result.newVersions, ...result.newRanges] + .map(({ name }) => name) + .filter(forbiddenDuplicatesFilter); if (problemPackages.length > 1) { console.log( From e1b71e142e908abdada7c0e5f7a63e896117d56e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 21 Dec 2022 16:01:50 +0100 Subject: [PATCH 40/94] cli: no longer treat workspace ranges as invalid Signed-off-by: Patrik Oldsberg --- .changeset/witty-eels-itch.md | 5 +++++ packages/cli/src/lib/versioning/Lockfile.ts | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 .changeset/witty-eels-itch.md diff --git a/.changeset/witty-eels-itch.md b/.changeset/witty-eels-itch.md new file mode 100644 index 0000000000..5a2da8b570 --- /dev/null +++ b/.changeset/witty-eels-itch.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Workspace ranges are no longer considered invalid by version commands. diff --git a/packages/cli/src/lib/versioning/Lockfile.ts b/packages/cli/src/lib/versioning/Lockfile.ts index ab6d98521f..ac2206cd83 100644 --- a/packages/cli/src/lib/versioning/Lockfile.ts +++ b/packages/cli/src/lib/versioning/Lockfile.ts @@ -178,7 +178,9 @@ export class Lockfile { } // Get rid of and signal any invalid ranges upfront - const invalid = allEntries.filter(e => !semver.validRange(e.range)); + const invalid = allEntries.filter( + e => !semver.validRange(e.range) && !e.range.startsWith('workspace:'), + ); result.invalidRanges.push( ...invalid.map(({ range }) => ({ name, range })), ); From 3e174587fcf293ceb00ae96ff92810f667622c33 Mon Sep 17 00:00:00 2001 From: Sarah Medeiros Date: Wed, 21 Dec 2022 10:39:25 -0500 Subject: [PATCH 41/94] Fix changeset Signed-off-by: Sarah Medeiros --- .changeset/funny-pianos-grow.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.changeset/funny-pianos-grow.md b/.changeset/funny-pianos-grow.md index 17f1d83927..7823ca82b0 100644 --- a/.changeset/funny-pianos-grow.md +++ b/.changeset/funny-pianos-grow.md @@ -3,7 +3,3 @@ --- Fixed bug in `EntityTagPicker` that filtered on unavailable tags for the selected kind. - -Fixed bug in `EntityOwnerPicker` that filtered on unavailable tags for the selected kind. - -Fixed bug in `EntityLifecyclePicker` that filtered on unavailable tags for the selected kind. From 5d05f14de63ac5812ea7a53cf0c00eaca57a54e5 Mon Sep 17 00:00:00 2001 From: Sarah Medeiros Date: Wed, 21 Dec 2022 10:52:49 -0500 Subject: [PATCH 42/94] Add missing changeset Signed-off-by: Sarah Medeiros --- plugins/catalog-react/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md index 45bd94a1a7..13fb46fa91 100644 --- a/plugins/catalog-react/CHANGELOG.md +++ b/plugins/catalog-react/CHANGELOG.md @@ -4,6 +4,7 @@ ### Patch Changes +- 2cb9998: Fixed bug in `EntityOwnerPicker` and `EntityLifecyclePicker` that filtered on unavailable tags for the selected kind. - 2e701b3796: Internal refactor to use `react-router-dom` rather than `react-router`. - 6ffa47bb0a: Cleanup and small fixes for the kind selector - 19356df560: Updated dependency `zen-observable` to `^0.9.0`. From fbdb48941fe0e83130b13cb2b73ac28c6e0362f8 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 21 Dec 2022 16:41:35 +0100 Subject: [PATCH 43/94] cli: use package graph to look up local lockfile versions Signed-off-by: Patrik Oldsberg --- .changeset/bright-pants-cry.md | 2 +- .../cli/src/commands/start/startFrontend.ts | 4 + packages/cli/src/commands/versions/bump.ts | 5 ++ packages/cli/src/commands/versions/lint.ts | 4 + .../cli/src/lib/versioning/Lockfile.test.ts | 79 ++++++++++++++++++- packages/cli/src/lib/versioning/Lockfile.ts | 38 ++++++--- 6 files changed, 118 insertions(+), 14 deletions(-) diff --git a/.changeset/bright-pants-cry.md b/.changeset/bright-pants-cry.md index 53ba0c9521..88d7356640 100644 --- a/.changeset/bright-pants-cry.md +++ b/.changeset/bright-pants-cry.md @@ -2,4 +2,4 @@ '@backstage/cli': patch --- -Fixing the error `Error: No existing version was accepted for range ^0.12.0, searching through 0.11.2,0.0.0-use.local, for package @backstage/core-components`, which can happen when there are multiple matching versions for a package, and one of them uses `workspace:^` as its range. +Fixed an issue where the CLI would fail to function when there was a mix of workspace and non-workspace versions of the same package in `yarn.lock` when using Yarn 3. diff --git a/packages/cli/src/commands/start/startFrontend.ts b/packages/cli/src/commands/start/startFrontend.ts index 689fb28466..b7b81e6bff 100644 --- a/packages/cli/src/commands/start/startFrontend.ts +++ b/packages/cli/src/commands/start/startFrontend.ts @@ -22,6 +22,7 @@ import { loadCliConfig } from '../../lib/config'; import { paths } from '../../lib/paths'; import { Lockfile } from '../../lib/versioning'; import { forbiddenDuplicatesFilter, includedFilter } from '../versions/lint'; +import { PackageGraph } from '../../lib/monorepo'; interface StartAppOptions { verifyVersions?: boolean; @@ -36,6 +37,9 @@ export async function startFrontend(options: StartAppOptions) { const lockfile = await Lockfile.load(paths.resolveTargetRoot('yarn.lock')); const result = lockfile.analyze({ filter: includedFilter, + localPackages: PackageGraph.fromPackages( + await PackageGraph.listTargetPackages(), + ), }); const problemPackages = [...result.newVersions, ...result.newRanges] .map(({ name }) => name) diff --git a/packages/cli/src/commands/versions/bump.ts b/packages/cli/src/commands/versions/bump.ts index 1905aded20..37eb858dcf 100644 --- a/packages/cli/src/commands/versions/bump.ts +++ b/packages/cli/src/commands/versions/bump.ts @@ -39,6 +39,7 @@ import { ReleaseManifest, } from '@backstage/release-manifests'; import 'global-agent/bootstrap'; +import { PackageGraph } from '../../lib/monorepo'; const DEP_TYPES = [ 'dependencies', @@ -305,8 +306,12 @@ export default async (opts: OptionValues) => { // Finally we make sure the new lockfile doesn't have any duplicates const dedupLockfile = await Lockfile.load(lockfilePath); + const result = dedupLockfile.analyze({ filter, + localPackages: PackageGraph.fromPackages( + await PackageGraph.listTargetPackages(), + ), }); if (result.newVersions.length > 0) { diff --git a/packages/cli/src/commands/versions/lint.ts b/packages/cli/src/commands/versions/lint.ts index 9939b2ba51..e6bc119b71 100644 --- a/packages/cli/src/commands/versions/lint.ts +++ b/packages/cli/src/commands/versions/lint.ts @@ -18,6 +18,7 @@ import { OptionValues } from 'commander'; import { Lockfile } from '../../lib/versioning'; import { paths } from '../../lib/paths'; import partition from 'lodash/partition'; +import { PackageGraph } from '../../lib/monorepo'; // Packages that we try to avoid duplicates for const INCLUDED = [/^@backstage\//]; @@ -55,6 +56,9 @@ export default async (cmd: OptionValues) => { const lockfile = await Lockfile.load(paths.resolveTargetRoot('yarn.lock')); const result = lockfile.analyze({ filter: includedFilter, + localPackages: PackageGraph.fromPackages( + await PackageGraph.listTargetPackages(), + ), }); logArray( diff --git a/packages/cli/src/lib/versioning/Lockfile.test.ts b/packages/cli/src/lib/versioning/Lockfile.test.ts index 4621b43372..e663ec4a93 100644 --- a/packages/cli/src/lib/versioning/Lockfile.test.ts +++ b/packages/cli/src/lib/versioning/Lockfile.test.ts @@ -16,6 +16,7 @@ import fs from 'fs-extra'; import mockFs from 'mock-fs'; +import { ExtendedPackage } from '../monorepo'; import { Lockfile } from './Lockfile'; const HEADER = `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. @@ -91,7 +92,7 @@ describe('Lockfile', () => { }); const lockfile = await Lockfile.load('/yarn.lock'); - const result = lockfile.analyze(); + const result = lockfile.analyze({ localPackages: new Map() }); expect(result).toEqual({ invalidRanges: [], newRanges: [], @@ -120,7 +121,7 @@ describe('Lockfile', () => { }); const lockfile = await Lockfile.load('/yarn.lock'); - const result = lockfile.analyze(); + const result = lockfile.analyze({ localPackages: new Map() }); expect(result).toEqual({ invalidRanges: [], newRanges: [ @@ -192,6 +193,36 @@ b@^2: version: 2.0.1 `; +const mockANewLocal = `${newHeader} +a@^1: + version: 1.0.1 + dependencies: + b: ^2 + integrity: sha512-xyz + resolved: "https://my-registry/a-1.0.01.tgz#abc123" + +"b@2.0.x, b@^2.0.1": + version: 0.0.0-use.local + +b@^2: + version: 2.0.0 +`; + +const mockANewLocalDedup = `${newHeader} +a@^1: + version: 1.0.1 + dependencies: + b: ^2 + integrity: sha512-xyz + resolved: "https://my-registry/a-1.0.01.tgz#abc123" + +"b@2.0.x, b@^2.0.1": + version: 0.0.0-use.local + +b@^2: + version: 0.0.0-use.local +`; + describe('New Lockfile', () => { afterEach(() => { mockFs.restore(); @@ -218,7 +249,7 @@ describe('New Lockfile', () => { }); const lockfile = await Lockfile.load('/yarn.lock'); - const result = lockfile.analyze(); + const result = lockfile.analyze({ localPackages: new Map() }); expect(result).toEqual({ invalidRanges: [], newRanges: [], @@ -242,4 +273,46 @@ describe('New Lockfile', () => { mockANewDedup, ); }); + + it('should deduplicate and save mockANewLocal', async () => { + mockFs({ + '/yarn.lock': mockANewLocal, + }); + + const lockfile = await Lockfile.load('/yarn.lock'); + const result = lockfile.analyze({ + localPackages: new Map([ + [ + 'b', + { + packageJson: { version: '2.0.1' }, + } as ExtendedPackage, + ], + ]), + }); + expect(result).toEqual({ + invalidRanges: [], + newRanges: [], + newVersions: [ + { + name: 'b', + range: '^2', + oldVersion: '2.0.0', + newVersion: '0.0.0-use.local', + }, + ], + }); + + expect(lockfile.toString()).toBe(mockANewLocal); + lockfile.replaceVersions(result.newVersions); + expect(lockfile.toString()).toBe(mockANewLocalDedup); + + await expect(fs.readFile('/yarn.lock', 'utf8')).resolves.toBe( + mockANewLocal, + ); + await expect(lockfile.save()).resolves.toBeUndefined(); + await expect(fs.readFile('/yarn.lock', 'utf8')).resolves.toBe( + mockANewLocalDedup, + ); + }); }); diff --git a/packages/cli/src/lib/versioning/Lockfile.ts b/packages/cli/src/lib/versioning/Lockfile.ts index ac2206cd83..0559ca86a1 100644 --- a/packages/cli/src/lib/versioning/Lockfile.ts +++ b/packages/cli/src/lib/versioning/Lockfile.ts @@ -18,6 +18,7 @@ import fs from 'fs-extra'; import semver from 'semver'; import { parseSyml, stringifySyml } from '@yarnpkg/parsers'; import { stringify as legacyStringifyLockfile } from '@yarnpkg/lockfile'; +import { ExtendedPackage } from '../monorepo'; const ENTRY_PATTERN = /^((?:@[^/]+\/)?[^@/]+)@(.+)$/; @@ -164,8 +165,11 @@ export class Lockfile { } /** Analyzes the lockfile to identify possible actions and warnings for the entries */ - analyze(options?: { filter?: (name: string) => boolean }): AnalyzeResult { - const { filter } = options ?? {}; + analyze(options: { + filter?: (name: string) => boolean; + localPackages: Map; + }): AnalyzeResult { + const { filter, localPackages } = options; const result: AnalyzeResult = { invalidRanges: [], newVersions: [], @@ -195,38 +199,52 @@ export class Lockfile { const versions = Array.from(new Set(entries.map(e => e.version))) .map(v => { // Translate workspace:^ references to the actual version - return v === '0.0.0-use.local' - ? require(`${name}/package.json`).version - : v; + if (v === '0.0.0-use.local') { + const local = localPackages.get(name); + if (!local) { + throw new Error(`No local package found for ${name}`); + } + if (!local.packageJson.version) { + throw new Error(`No version found for local package ${name}`); + } + return { + entryVersion: v, + actualVersion: local.packageJson.version, + }; + } + return { entryVersion: v, actualVersion: v }; }) - .sort((v1, v2) => semver.rcompare(v1, v2)); + .sort((v1, v2) => semver.rcompare(v1.actualVersion, v2.actualVersion)); // If we're not using at least 2 different versions we're done if (versions.length < 2) { continue; } + // TODO(Rugvip): Support bumping into workspace ranges too const acceptedVersions = new Set(); for (const { version, range } of entries) { // Finds the highest matching version from the the known versions // TODO(Rugvip): We may want to select the version that satisfies the most ranges rather than the highest one - const acceptedVersion = versions.find(v => semver.satisfies(v, range)); + const acceptedVersion = versions.find(v => + semver.satisfies(v.actualVersion, range), + ); if (!acceptedVersion) { throw new Error( `No existing version was accepted for range ${range}, searching through ${versions}, for package ${name}`, ); } - if (acceptedVersion !== version) { + if (acceptedVersion.entryVersion !== version) { result.newVersions.push({ name, range, - newVersion: acceptedVersion, + newVersion: acceptedVersion.entryVersion, oldVersion: version, }); } - acceptedVersions.add(acceptedVersion); + acceptedVersions.add(acceptedVersion.actualVersion); } // If all ranges were able to accept the same version, we're done From b97b844835da33b1c74a2969fb4080c05d2dbf8d Mon Sep 17 00:00:00 2001 From: Enrico Alvarenga Date: Tue, 6 Dec 2022 12:18:25 -0800 Subject: [PATCH 44/94] feat: add EntityFilterQuery to EntityPicker to allow filtering entities Signed-off-by: Enrico Alvarenga --- .../fields/EntityPicker/EntityPicker.test.tsx | 104 +++++++++++++++++- .../fields/EntityPicker/EntityPicker.tsx | 8 +- .../components/fields/EntityPicker/schema.ts | 12 ++ 3 files changed, 121 insertions(+), 3 deletions(-) diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx index 942eef9d82..b6d6688a09 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx @@ -14,6 +14,7 @@ * limitations under the License. */ +import { EntityFilterQuery } from '@backstage/catalog-client'; import { Entity } from '@backstage/catalog-model'; import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react'; import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; @@ -34,7 +35,13 @@ describe('', () => { const schema = {}; const required = false; let uiSchema: { - 'ui:options': { allowedKinds?: string[]; defaultKind?: string }; + 'ui:options': { + allowedKinds?: string[]; + defaultKind?: string; + allowArbitraryValues?: boolean; + defaultNamespace?: string | false; + catalogFilter?: EntityFilterQuery; + }; }; const rawErrors: string[] = []; const formData = undefined; @@ -66,7 +73,7 @@ describe('', () => { afterEach(() => jest.resetAllMocks()); - describe('without allowedKinds', () => { + describe('without allowedKinds and catalogFilter', () => { beforeEach(() => { uiSchema = { 'ui:options': {} }; props = { @@ -136,4 +143,97 @@ describe('', () => { }); }); }); + + describe('with catalogFilter', () => { + beforeEach(() => { + uiSchema = { + 'ui:options': { + catalogFilter: [ + { + kind: ['Group'], + 'metadata.name': 'test-entity', + }, + { + kind: ['User'], + 'metadata.name': 'test-entity', + }, + ], + }, + }; + props = { + onChange, + schema, + required, + uiSchema, + rawErrors, + formData, + } as unknown as FieldProps; + + catalogApi.getEntities.mockResolvedValue({ items: entities }); + }); + + it('searches for a specific group entity', async () => { + await renderInTestApp( + + + , + ); + + expect(catalogApi.getEntities).toHaveBeenCalledWith({ + filter: [ + { + kind: ['Group'], + 'metadata.name': 'test-entity', + }, + { + kind: ['User'], + 'metadata.name': 'test-entity', + }, + ], + }); + }); + }); + + describe('catalogFilter should take precedence over allowedKinds', () => { + beforeEach(() => { + uiSchema = { + 'ui:options': { + catalogFilter: [ + { + kind: ['Group'], + 'metadata.name': 'test-group', + }, + ], + allowedKinds: ['User'], + }, + }; + props = { + onChange, + schema, + required, + uiSchema, + rawErrors, + formData, + } as unknown as FieldProps; + + catalogApi.getEntities.mockResolvedValue({ items: entities }); + }); + + it('searches for a Group entity', async () => { + await renderInTestApp( + + + , + ); + + expect(catalogApi.getEntities).toHaveBeenCalledWith({ + filter: [ + { + kind: ['Group'], + 'metadata.name': 'test-group', + }, + ], + }); + }); + }); }); diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx index bc78e00576..48e60b60ad 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { type EntityFilterQuery } from '@backstage/catalog-client'; import { useApi } from '@backstage/core-plugin-api'; import { catalogApiRef, @@ -44,6 +45,11 @@ export const EntityPicker = (props: EntityPickerProps) => { idSchema, } = props; const allowedKinds = uiSchema['ui:options']?.allowedKinds; + + const catalogFilter: EntityFilterQuery | undefined = + uiSchema['ui:options']?.catalogFilter || + (allowedKinds && { kind: allowedKinds }); + const defaultKind = uiSchema['ui:options']?.defaultKind; const defaultNamespace = uiSchema['ui:options']?.defaultNamespace; @@ -51,7 +57,7 @@ export const EntityPicker = (props: EntityPickerProps) => { const { value: entities, loading } = useAsync(() => catalogApi.getEntities( - allowedKinds ? { filter: { kind: allowedKinds } } : undefined, + catalogFilter ? { filter: catalogFilter } : undefined, ), ); diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/schema.ts b/plugins/scaffolder/src/components/fields/EntityPicker/schema.ts index 9236457fc0..38fc0c9dc2 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/schema.ts +++ b/plugins/scaffolder/src/components/fields/EntityPicker/schema.ts @@ -16,6 +16,13 @@ import { z } from 'zod'; import { makeFieldSchemaFromZod } from '../utils'; +/** + * @public + */ +export const entityQueryFilterExpressionSchema = z.record( + z.string().or(z.array(z.string())), +); + /** * @public */ @@ -42,6 +49,11 @@ export const EntityPickerFieldSchema = makeFieldSchemaFromZod( .describe( 'The default namespace. Options with this namespace will not be prefixed.', ), + catalogFilter: z + .array(entityQueryFilterExpressionSchema) + .or(entityQueryFilterExpressionSchema) + .optional() + .describe('List of key-value filter expression for entities'), }), ); From 4153796f6e4ad30d7778c38d52c22ce08d48a421 Mon Sep 17 00:00:00 2001 From: Enrico Alvarenga Date: Tue, 6 Dec 2022 12:19:18 -0800 Subject: [PATCH 45/94] feat: add EntityFilterQuery to OwnerPicker to allow filtering entities Signed-off-by: Enrico Alvarenga --- .../fields/OwnerPicker/OwnerPicker.test.tsx | 104 +++++++++++++++++- .../fields/OwnerPicker/OwnerPicker.tsx | 10 +- .../components/fields/OwnerPicker/schema.ts | 6 + 3 files changed, 113 insertions(+), 7 deletions(-) diff --git a/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.test.tsx b/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.test.tsx index b78d3add9e..00508593f5 100644 --- a/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.test.tsx @@ -14,6 +14,7 @@ * limitations under the License. */ +import { type EntityFilterQuery } from '@backstage/catalog-client'; import { Entity } from '@backstage/catalog-model'; import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react'; import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; @@ -32,7 +33,15 @@ describe('', () => { const onChange = jest.fn(); const schema = {}; const required = false; - let uiSchema: { 'ui:options': { allowedKinds?: string[] } }; + let uiSchema: { + 'ui:options': { + allowedKinds?: string[]; + defaultKind?: string; + allowArbitraryValues?: boolean; + defaultNamespace?: string | false; + catalogFilter?: EntityFilterQuery; + }; + }; const rawErrors: string[] = []; const formData = undefined; @@ -63,7 +72,7 @@ describe('', () => { afterEach(() => jest.resetAllMocks()); - describe('without allowedKinds', () => { + describe('without catalogFilter and allowedKinds', () => { beforeEach(() => { uiSchema = { 'ui:options': {} }; props = { @@ -108,7 +117,7 @@ describe('', () => { catalogApi.getEntities.mockResolvedValue({ items: entities }); }); - it('searches for users and groups', async () => { + it('searches for users', async () => { await renderInTestApp( @@ -122,4 +131,93 @@ describe('', () => { }); }); }); + + describe('with catalogFilter', () => { + beforeEach(() => { + uiSchema = { + 'ui:options': { + catalogFilter: [ + { + kind: ['Group'], + 'spec.type': 'team', + }, + ], + }, + }; + props = { + onChange, + schema, + required, + uiSchema, + rawErrors, + formData, + } as unknown as FieldProps; + + catalogApi.getEntities.mockResolvedValue({ items: entities }); + }); + + it('searches for group entities of type team', async () => { + await renderInTestApp( + + + , + ); + + expect(catalogApi.getEntities).toHaveBeenCalledWith({ + filter: [ + { + kind: ['Group'], + 'spec.type': 'team', + }, + ], + }); + }); + }); + + describe('catalogFilter should take precedence over allowedKinds', () => { + beforeEach(() => { + uiSchema = { + 'ui:options': { + allowedKinds: ['User'], + catalogFilter: [ + { + kind: ['Group', 'User'], + }, + { + 'spec.type': ['team', 'business-unit'], + }, + ], + }, + }; + props = { + onChange, + schema, + required, + uiSchema, + rawErrors, + formData, + } as unknown as FieldProps; + + catalogApi.getEntities.mockResolvedValue({ items: entities }); + }); + + it('searches for users and groups or teams and business units', async () => { + await renderInTestApp( + + + , + ); + + expect(catalogApi.getEntities).toHaveBeenCalledWith({ + filter: [ + { + kind: ['Group', 'User'], + }, + { + 'spec.type': ['team', 'business-unit'], + }, + ], + }); + }); + }); }); diff --git a/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx b/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx index 1c4c356906..5c69e73e12 100644 --- a/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx +++ b/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx @@ -33,14 +33,16 @@ export const OwnerPicker = (props: OwnerPickerProps) => { } = props; const defaultNamespace = uiSchema['ui:options']?.defaultNamespace; + const allowedKinds = uiSchema['ui:options']?.allowedKinds; + + const catalogFilter = uiSchema['ui:options']?.catalogFilter || { + kind: allowedKinds || ['Group', 'User'], + }; const ownerUiSchema = { ...uiSchema, 'ui:options': { - allowedKinds: (uiSchema['ui:options']?.allowedKinds || [ - 'Group', - 'User', - ]) as string[], + catalogFilter, defaultKind: 'Group', allowArbitraryValues: uiSchema['ui:options']?.allowArbitraryValues ?? true, diff --git a/plugins/scaffolder/src/components/fields/OwnerPicker/schema.ts b/plugins/scaffolder/src/components/fields/OwnerPicker/schema.ts index 56edef1343..4e9ca8ca31 100644 --- a/plugins/scaffolder/src/components/fields/OwnerPicker/schema.ts +++ b/plugins/scaffolder/src/components/fields/OwnerPicker/schema.ts @@ -15,6 +15,7 @@ */ import { z } from 'zod'; import { makeFieldSchemaFromZod } from '../utils'; +import { entityQueryFilterExpressionSchema } from '../EntityPicker/schema'; /** * @public @@ -39,6 +40,11 @@ export const OwnerPickerFieldSchema = makeFieldSchemaFromZod( .describe( 'The default namespace. Options with this namespace will not be prefixed.', ), + catalogFilter: z + .array(entityQueryFilterExpressionSchema) + .or(entityQueryFilterExpressionSchema) + .optional() + .describe('List of key-value filter expression for entities'), }), ); From 168045cc5867b2a6dfe1cb83361b23d2098c4964 Mon Sep 17 00:00:00 2001 From: Enrico Alvarenga Date: Tue, 6 Dec 2022 17:11:01 -0800 Subject: [PATCH 46/94] docs: update OwnerPicker and EntityPicker API docs Signed-off-by: Enrico Alvarenga --- plugins/scaffolder/api-report.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index 39e58a7a42..3ab0a329cd 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -88,6 +88,10 @@ export const EntityPickerFieldExtension: FieldExtensionComponent< defaultNamespace?: string | false | undefined; allowedKinds?: string[] | undefined; allowArbitraryValues?: boolean | undefined; + catalogFilter?: + | Record + | Record[] + | undefined; } >; @@ -99,6 +103,10 @@ export const EntityPickerFieldSchema: FieldSchema< defaultNamespace?: string | false | undefined; allowedKinds?: string[] | undefined; allowArbitraryValues?: boolean | undefined; + catalogFilter?: + | Record + | Record[] + | undefined; } >; @@ -309,6 +317,10 @@ export const OwnerPickerFieldExtension: FieldExtensionComponent< defaultNamespace?: string | false | undefined; allowedKinds?: string[] | undefined; allowArbitraryValues?: boolean | undefined; + catalogFilter?: + | Record + | Record[] + | undefined; } >; @@ -319,6 +331,10 @@ export const OwnerPickerFieldSchema: FieldSchema< defaultNamespace?: string | false | undefined; allowedKinds?: string[] | undefined; allowArbitraryValues?: boolean | undefined; + catalogFilter?: + | Record + | Record[] + | undefined; } >; From e4c02404454e68704dba1a351d5682880b4fd09e Mon Sep 17 00:00:00 2001 From: Enrico Alvarenga Date: Thu, 8 Dec 2022 10:30:25 -0800 Subject: [PATCH 47/94] chore: add a changeset Signed-off-by: Enrico Alvarenga --- .changeset/selfish-phones-smoke.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/selfish-phones-smoke.md diff --git a/.changeset/selfish-phones-smoke.md b/.changeset/selfish-phones-smoke.md new file mode 100644 index 0000000000..a9583cff94 --- /dev/null +++ b/.changeset/selfish-phones-smoke.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Added a new prop to OwnerPicker and EntityPicker components to support filtering options by any field(s) of an entity. From 1cd1c5cbb0e0be4523560bd2a31e164b52dfcf53 Mon Sep 17 00:00:00 2001 From: Enrico Alvarenga Date: Mon, 19 Dec 2022 12:53:38 -0800 Subject: [PATCH 48/94] docs: update docs referencing OwnerPicker and EntityPicker Signed-off-by: Enrico Alvarenga --- .changeset/selfish-phones-smoke.md | 30 ++++++++++++++++++- .../software-templates/writing-templates.md | 10 +++---- .../README.md | 8 ++--- .../scaffolder-backend-module-rails/README.md | 8 ++--- .../README.md | 8 ++--- 5 files changed, 46 insertions(+), 18 deletions(-) diff --git a/.changeset/selfish-phones-smoke.md b/.changeset/selfish-phones-smoke.md index a9583cff94..bb6f7f7211 100644 --- a/.changeset/selfish-phones-smoke.md +++ b/.changeset/selfish-phones-smoke.md @@ -2,4 +2,32 @@ '@backstage/plugin-scaffolder': patch --- -Added a new prop to OwnerPicker and EntityPicker components to support filtering options by any field(s) of an entity. +Added the catalogFilter prop to OwnerPicker and EntityPicker components to support filtering options by any field(s) of an entity. + +The `allowedKinds` field has been deprecated. Use `catalogFilter` instead. This field allows users to specify a filter on the shape of [EntityFilterQuery](https://github.com/backstage/backstage/blob/774c42003782121d3d6b2aa5f2865d53370c160e/packages/catalog-client/src/types/api.ts#L74), which can be passed into the CatalogClient. See examples below: + +- Get all entities of kind `Group` + + ```yaml + owner: + title: Owner + type: string + description: Owner of the component + ui:field: OwnerPicker + ui:options: + catalogFilter: + - kind: Group + ``` + +- Get entities of kind `Group` and spec.type `team` + ```yaml + owner: + title: Owner + type: string + description: Owner of the component + ui:field: OwnerPicker + ui:options: + catalogFilter: + - kind: Group + spec.type: team + ``` diff --git a/docs/features/software-templates/writing-templates.md b/docs/features/software-templates/writing-templates.md index 826121bd02..d86ec33e7a 100644 --- a/docs/features/software-templates/writing-templates.md +++ b/docs/features/software-templates/writing-templates.md @@ -44,8 +44,8 @@ spec: description: Owner of the component ui:field: OwnerPicker ui:options: - allowedKinds: - - Group + catalogFilter: + kind: Group - title: Choose a location required: - repoUrl @@ -459,7 +459,7 @@ an owner for them. Ideally, users should be able to select an owner when they go through the scaffolder form from the users and groups already known to Backstage. The `OwnerPicker` is a custom field that generates a searchable list of groups and/or users already in the catalog to pick an owner from. You can -specify which of the two kinds are listed in the `allowedKinds` option: +specify which of the two kinds (or both) are listed in the `catalogFilter.kind` option: ```yaml owner: @@ -468,8 +468,8 @@ owner: description: Owner of the component ui:field: OwnerPicker ui:options: - allowedKinds: - - Group + catalogFilter: + kind: [Group, User] ``` ## `spec.steps` - `Action[]` diff --git a/plugins/scaffolder-backend-module-cookiecutter/README.md b/plugins/scaffolder-backend-module-cookiecutter/README.md index 642ef25edf..c96136a0f9 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/README.md +++ b/plugins/scaffolder-backend-module-cookiecutter/README.md @@ -73,16 +73,16 @@ spec: description: Owner of the component ui:field: OwnerPicker ui:options: - allowedKinds: - - Group + catalogFilter: + kind: Group system: title: System type: string description: System of the component ui:field: EntityPicker ui:options: - allowedKinds: - - System + catalogFilter: + kind: System defaultKind: System - title: Choose a location diff --git a/plugins/scaffolder-backend-module-rails/README.md b/plugins/scaffolder-backend-module-rails/README.md index 9d1316473d..066aa0cedc 100644 --- a/plugins/scaffolder-backend-module-rails/README.md +++ b/plugins/scaffolder-backend-module-rails/README.md @@ -74,16 +74,16 @@ spec: description: Owner of the component ui:field: OwnerPicker ui:options: - allowedKinds: - - Group + catalogFilter: + kind: Group system: title: System type: string description: System of the component ui:field: EntityPicker ui:options: - allowedKinds: - - System + catalogFilter: + kind: System defaultKind: System - title: Choose a location diff --git a/plugins/scaffolder-backend-module-yeoman/README.md b/plugins/scaffolder-backend-module-yeoman/README.md index 8ad3a74fe2..aee83ebe0a 100644 --- a/plugins/scaffolder-backend-module-yeoman/README.md +++ b/plugins/scaffolder-backend-module-yeoman/README.md @@ -73,16 +73,16 @@ spec: description: Owner of the component ui:field: OwnerPicker ui:options: - allowedKinds: - - Group + catalogFilter: + kind: Group system: title: System type: string description: System of the component ui:field: EntityPicker ui:options: - allowedKinds: - - System + catalogFilter: + kind: System defaultKind: System - title: Choose a location From 981e34be32b89d95de05fe95759e758520b9ce13 Mon Sep 17 00:00:00 2001 From: Enrico Alvarenga Date: Mon, 19 Dec 2022 12:55:02 -0800 Subject: [PATCH 49/94] docs: mark allowedKinds as deprecated Signed-off-by: Enrico Alvarenga --- .../src/components/fields/EntityPicker/schema.ts | 7 ++++++- .../scaffolder/src/components/fields/OwnerPicker/schema.ts | 5 ++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/schema.ts b/plugins/scaffolder/src/components/fields/EntityPicker/schema.ts index 38fc0c9dc2..f83dfbf817 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/schema.ts +++ b/plugins/scaffolder/src/components/fields/EntityPicker/schema.ts @@ -29,10 +29,15 @@ export const entityQueryFilterExpressionSchema = z.record( export const EntityPickerFieldSchema = makeFieldSchemaFromZod( z.string(), z.object({ + /** + * @deprecated Use `catalogFilter` instead. + */ allowedKinds: z .array(z.string()) .optional() - .describe('List of kinds of entities to derive options from'), + .describe( + 'DEPRECATED: Use `catalogFilter` instead. List of kinds of entities to derive options from', + ), defaultKind: z .string() .optional() diff --git a/plugins/scaffolder/src/components/fields/OwnerPicker/schema.ts b/plugins/scaffolder/src/components/fields/OwnerPicker/schema.ts index 4e9ca8ca31..0dbe2fa74f 100644 --- a/plugins/scaffolder/src/components/fields/OwnerPicker/schema.ts +++ b/plugins/scaffolder/src/components/fields/OwnerPicker/schema.ts @@ -23,12 +23,15 @@ import { entityQueryFilterExpressionSchema } from '../EntityPicker/schema'; export const OwnerPickerFieldSchema = makeFieldSchemaFromZod( z.string(), z.object({ + /** + * @deprecated Use `catalogFilter` instead. + */ allowedKinds: z .array(z.string()) .default(['Group', 'User']) .optional() .describe( - 'List of kinds of entities to derive options from. Defaults to Group and User', + 'DEPRECATED: Use `catalogFilter` instead. List of kinds of entities to derive options from. Defaults to Group and User', ), allowArbitraryValues: z .boolean() From 15b3c2b153bb4afb662f739185053a06381ab522 Mon Sep 17 00:00:00 2001 From: Enrico Alvarenga Date: Mon, 19 Dec 2022 12:56:04 -0800 Subject: [PATCH 50/94] chore: update sample templates to use catalogFilter instead of allowedKinds Signed-off-by: Enrico Alvarenga --- .../app/src/components/scaffolder/defaultPreviewTemplate.ts | 4 ++-- .../sample-templates/bitbucket-demo/template.yaml | 4 ++-- .../components/TemplateEditorPage/TemplateFormPreviewer.tsx | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/app/src/components/scaffolder/defaultPreviewTemplate.ts b/packages/app/src/components/scaffolder/defaultPreviewTemplate.ts index 56f34779de..5d8a2accda 100644 --- a/packages/app/src/components/scaffolder/defaultPreviewTemplate.ts +++ b/packages/app/src/components/scaffolder/defaultPreviewTemplate.ts @@ -31,8 +31,8 @@ parameters: description: Owner of the component ui:field: OwnerPicker ui:options: - allowedKinds: - - Group + catalogFilter: + kind: Group - title: Choose a location required: - repoUrl diff --git a/plugins/scaffolder-backend/sample-templates/bitbucket-demo/template.yaml b/plugins/scaffolder-backend/sample-templates/bitbucket-demo/template.yaml index 52714f6d73..64dda93b07 100644 --- a/plugins/scaffolder-backend/sample-templates/bitbucket-demo/template.yaml +++ b/plugins/scaffolder-backend/sample-templates/bitbucket-demo/template.yaml @@ -39,8 +39,8 @@ spec: description: Owner of the component ui:field: OwnerPicker ui:options: - allowedKinds: - - Group + catalogFilter: + kind: Group steps: - id: fetch-base diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateFormPreviewer.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateFormPreviewer.tsx index 041b7797ee..5bc32ed581 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateFormPreviewer.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateFormPreviewer.tsx @@ -53,8 +53,8 @@ parameters: description: Owner of the component ui:field: OwnerPicker ui:options: - allowedKinds: - - Group + catalogFilter: + kind: Group - title: Choose a location required: - repoUrl From 1225bdf28fe43ebb7cc521ae39f4fc1269b34795 Mon Sep 17 00:00:00 2001 From: Enrico Alvarenga Date: Mon, 19 Dec 2022 13:06:52 -0800 Subject: [PATCH 51/94] fix: attempt to fix doc quality Signed-off-by: Enrico Alvarenga --- .changeset/selfish-phones-smoke.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/selfish-phones-smoke.md b/.changeset/selfish-phones-smoke.md index bb6f7f7211..c3924adb68 100644 --- a/.changeset/selfish-phones-smoke.md +++ b/.changeset/selfish-phones-smoke.md @@ -2,7 +2,7 @@ '@backstage/plugin-scaffolder': patch --- -Added the catalogFilter prop to OwnerPicker and EntityPicker components to support filtering options by any field(s) of an entity. +Added `catalogFilter` field to OwnerPicker and EntityPicker components to support filtering options by any field(s) of an entity. The `allowedKinds` field has been deprecated. Use `catalogFilter` instead. This field allows users to specify a filter on the shape of [EntityFilterQuery](https://github.com/backstage/backstage/blob/774c42003782121d3d6b2aa5f2865d53370c160e/packages/catalog-client/src/types/api.ts#L74), which can be passed into the CatalogClient. See examples below: From 2c895a92a5d19e911d2259897449b0173ab16a15 Mon Sep 17 00:00:00 2001 From: Enrico Alvarenga Date: Wed, 21 Dec 2022 12:53:10 -0800 Subject: [PATCH 52/94] fix: update changeset to minor Co-authored-by: Johan Haals Signed-off-by: Enrico Alvarenga Signed-off-by: Enrico Alvarenga --- .changeset/selfish-phones-smoke.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/selfish-phones-smoke.md b/.changeset/selfish-phones-smoke.md index c3924adb68..19a686c77e 100644 --- a/.changeset/selfish-phones-smoke.md +++ b/.changeset/selfish-phones-smoke.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-scaffolder': patch +'@backstage/plugin-scaffolder': minor --- Added `catalogFilter` field to OwnerPicker and EntityPicker components to support filtering options by any field(s) of an entity. From 4f02e664fbbee9928bccd82d08c83dd5dddd19f6 Mon Sep 17 00:00:00 2001 From: Enrico Alvarenga Date: Wed, 21 Dec 2022 13:00:40 -0800 Subject: [PATCH 53/94] fix: include other modified packages in the changeset Signed-off-by: Enrico Alvarenga --- .changeset/selfish-phones-smoke.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.changeset/selfish-phones-smoke.md b/.changeset/selfish-phones-smoke.md index 19a686c77e..42f33f9869 100644 --- a/.changeset/selfish-phones-smoke.md +++ b/.changeset/selfish-phones-smoke.md @@ -1,5 +1,9 @@ --- '@backstage/plugin-scaffolder': minor +'@backstage/plugin-scaffolder-backend-module-cookiecutter': patch +'@backstage/plugin-scaffolder-backend-module-rails': patch +'@backstage/plugin-scaffolder-backend-module-yeoman': patch +'@backstage/plugin-scaffolder-backend': patch --- Added `catalogFilter` field to OwnerPicker and EntityPicker components to support filtering options by any field(s) of an entity. From bafe99abf9161bbc9cb7fedd11f76da6cc203792 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 21 Dec 2022 23:44:44 +0000 Subject: [PATCH 54/94] fix(deps): update aws-sdk-js-v3 monorepo to v3.236.0 Signed-off-by: Renovate Bot --- yarn.lock | 138 +++++++++++++++++++++++++++--------------------------- 1 file changed, 69 insertions(+), 69 deletions(-) diff --git a/yarn.lock b/yarn.lock index e45abbd492..5562f1fa30 100644 --- a/yarn.lock +++ b/yarn.lock @@ -408,15 +408,15 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/client-cognito-identity@npm:3.235.0": - version: 3.235.0 - resolution: "@aws-sdk/client-cognito-identity@npm:3.235.0" +"@aws-sdk/client-cognito-identity@npm:3.236.0": + version: 3.236.0 + resolution: "@aws-sdk/client-cognito-identity@npm:3.236.0" dependencies: "@aws-crypto/sha256-browser": 2.0.0 "@aws-crypto/sha256-js": 2.0.0 - "@aws-sdk/client-sts": 3.235.0 + "@aws-sdk/client-sts": 3.236.0 "@aws-sdk/config-resolver": 3.234.0 - "@aws-sdk/credential-provider-node": 3.235.0 + "@aws-sdk/credential-provider-node": 3.236.0 "@aws-sdk/fetch-http-handler": 3.226.0 "@aws-sdk/hash-node": 3.226.0 "@aws-sdk/invalid-dependency": 3.226.0 @@ -448,20 +448,20 @@ __metadata: "@aws-sdk/util-utf8-browser": 3.188.0 "@aws-sdk/util-utf8-node": 3.208.0 tslib: ^2.3.1 - checksum: 4637132cc1a95823867c5ee80b35c7b84ddeed83c0778c2d7c197b26f24899641c825926c886c293f36d62e6b72be03ee091ac11cc9dbd875ad85936548ee178 + checksum: 808339bd0113f65674dea0d65af25c350ff5a8f2595f9dd11215b6e457df5d243db156042dc4bfbb39b302d6f871a71fbbed20f403207fb6e4c9e444eb802bf1 languageName: node linkType: hard "@aws-sdk/client-s3@npm:^3.208.0": - version: 3.235.0 - resolution: "@aws-sdk/client-s3@npm:3.235.0" + version: 3.236.0 + resolution: "@aws-sdk/client-s3@npm:3.236.0" dependencies: "@aws-crypto/sha1-browser": 2.0.0 "@aws-crypto/sha256-browser": 2.0.0 "@aws-crypto/sha256-js": 2.0.0 - "@aws-sdk/client-sts": 3.235.0 + "@aws-sdk/client-sts": 3.236.0 "@aws-sdk/config-resolver": 3.234.0 - "@aws-sdk/credential-provider-node": 3.235.0 + "@aws-sdk/credential-provider-node": 3.236.0 "@aws-sdk/eventstream-serde-browser": 3.226.0 "@aws-sdk/eventstream-serde-config-resolver": 3.226.0 "@aws-sdk/eventstream-serde-node": 3.226.0 @@ -511,19 +511,19 @@ __metadata: "@aws-sdk/xml-builder": 3.201.0 fast-xml-parser: 4.0.11 tslib: ^2.3.1 - checksum: b16ce0a34a4d302edbc9cae91296a90a907511d6f0985feccf2196d537e1f1bfff6bfc0dafcb7ea1d5d7d0afd79d843cc15f8a5f87debd08852de338c698169b + checksum: 6a8953c229a1824bf1639dfa1a593bff3419c2c338da8df956d5cd3098dc3c1236ee63564347a22f40dc5947322b3f315ae7e39dd9c115ccf8a28199168c6d3b languageName: node linkType: hard "@aws-sdk/client-sqs@npm:^3.208.0": - version: 3.235.0 - resolution: "@aws-sdk/client-sqs@npm:3.235.0" + version: 3.236.0 + resolution: "@aws-sdk/client-sqs@npm:3.236.0" dependencies: "@aws-crypto/sha256-browser": 2.0.0 "@aws-crypto/sha256-js": 2.0.0 - "@aws-sdk/client-sts": 3.235.0 + "@aws-sdk/client-sts": 3.236.0 "@aws-sdk/config-resolver": 3.234.0 - "@aws-sdk/credential-provider-node": 3.235.0 + "@aws-sdk/credential-provider-node": 3.236.0 "@aws-sdk/fetch-http-handler": 3.226.0 "@aws-sdk/hash-node": 3.226.0 "@aws-sdk/invalid-dependency": 3.226.0 @@ -558,13 +558,13 @@ __metadata: "@aws-sdk/util-utf8-node": 3.208.0 fast-xml-parser: 4.0.11 tslib: ^2.3.1 - checksum: 04da2ab0a850fa74265b9ab91b14fdf470d7f2850e31866b3582b83a01991bd9555b370e34bceba2445910b2d60cf8e17d7ead1f5caa94987e4114db949548f8 + checksum: c1d3eb3886d8d799afd081cf76822627e359495daa94edb615a001704933b31d276249828c5e5823af49ba08826875a4c768d8e6b4493abe9b65583a585839b7 languageName: node linkType: hard -"@aws-sdk/client-sso-oidc@npm:3.235.0": - version: 3.235.0 - resolution: "@aws-sdk/client-sso-oidc@npm:3.235.0" +"@aws-sdk/client-sso-oidc@npm:3.236.0": + version: 3.236.0 + resolution: "@aws-sdk/client-sso-oidc@npm:3.236.0" dependencies: "@aws-crypto/sha256-browser": 2.0.0 "@aws-crypto/sha256-js": 2.0.0 @@ -599,13 +599,13 @@ __metadata: "@aws-sdk/util-utf8-browser": 3.188.0 "@aws-sdk/util-utf8-node": 3.208.0 tslib: ^2.3.1 - checksum: c62558375fb6d3ce44136019de99e5134805894a80cec191e145003c0e065eb52ba0a288a62884371d6a69370f92df522b120e1c5428520c09ba0f4cdc5b91b7 + checksum: 6fbbebb37547bafc9345c1fd03692728d6b6015f7583f22b2542beaac7f395ab29af884f1a353e78dd147ebf8b109b1d6678f9d1ae18c2b70739e9ceee6781ef languageName: node linkType: hard -"@aws-sdk/client-sso@npm:3.235.0": - version: 3.235.0 - resolution: "@aws-sdk/client-sso@npm:3.235.0" +"@aws-sdk/client-sso@npm:3.236.0": + version: 3.236.0 + resolution: "@aws-sdk/client-sso@npm:3.236.0" dependencies: "@aws-crypto/sha256-browser": 2.0.0 "@aws-crypto/sha256-js": 2.0.0 @@ -640,18 +640,18 @@ __metadata: "@aws-sdk/util-utf8-browser": 3.188.0 "@aws-sdk/util-utf8-node": 3.208.0 tslib: ^2.3.1 - checksum: 9852704cc383cbc8e44013eed6de514f60dc4f70ab89ceea79971b4b286db6b923648e8e4169d248f0888efed19c7c5fcc5748177577635d07080fd4980431d7 + checksum: 15cb25a3c12fe0f11ed320c8f02f72ee0d43291a14c06fce81f608fbc75a85785698fbffc3112e7e92ece25460d24670ba345535b43003b20798a0fcc8a7b31d languageName: node linkType: hard -"@aws-sdk/client-sts@npm:3.235.0, @aws-sdk/client-sts@npm:^3.208.0": - version: 3.235.0 - resolution: "@aws-sdk/client-sts@npm:3.235.0" +"@aws-sdk/client-sts@npm:3.236.0, @aws-sdk/client-sts@npm:^3.208.0": + version: 3.236.0 + resolution: "@aws-sdk/client-sts@npm:3.236.0" dependencies: "@aws-crypto/sha256-browser": 2.0.0 "@aws-crypto/sha256-js": 2.0.0 "@aws-sdk/config-resolver": 3.234.0 - "@aws-sdk/credential-provider-node": 3.235.0 + "@aws-sdk/credential-provider-node": 3.236.0 "@aws-sdk/fetch-http-handler": 3.226.0 "@aws-sdk/hash-node": 3.226.0 "@aws-sdk/invalid-dependency": 3.226.0 @@ -685,7 +685,7 @@ __metadata: "@aws-sdk/util-utf8-node": 3.208.0 fast-xml-parser: 4.0.11 tslib: ^2.3.1 - checksum: 9a2ec3240bd01fd1ed95b2c6766ea8d141e4b85a6535fcbb5c57ac725ef58c2d1c1403a61c336adbb1eceb5dd306ac1d35d7b8f25ed1ebc544a14ad14fb038a7 + checksum: 98463fe50a588032b82be5289445c6e7250bd947076e5981ba7c126386d567f3b1ea9a9652c032da919503cdba2c3b517061911ddffae0c91f880f7eabaeaa08 languageName: node linkType: hard @@ -702,15 +702,15 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/credential-provider-cognito-identity@npm:3.235.0": - version: 3.235.0 - resolution: "@aws-sdk/credential-provider-cognito-identity@npm:3.235.0" +"@aws-sdk/credential-provider-cognito-identity@npm:3.236.0": + version: 3.236.0 + resolution: "@aws-sdk/credential-provider-cognito-identity@npm:3.236.0" dependencies: - "@aws-sdk/client-cognito-identity": 3.235.0 + "@aws-sdk/client-cognito-identity": 3.236.0 "@aws-sdk/property-provider": 3.226.0 "@aws-sdk/types": 3.226.0 tslib: ^2.3.1 - checksum: 85d348faa5161282d226040315aaff1a754d3bbd23ccb9131f021a5f6a473be76b80e2b3f5132d3b0a119732822d6b76829626d64e6c71c4d61d1fcbeb6e0080 + checksum: 29d3178f490cbb711553103c0fb544604c5e70e36395f1edc6b9bd2ee0e928160b911b4893fe89418c182548d0dddfbd44e9ef629ad690ab1724a2a693368c16 languageName: node linkType: hard @@ -738,38 +738,38 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/credential-provider-ini@npm:3.235.0": - version: 3.235.0 - resolution: "@aws-sdk/credential-provider-ini@npm:3.235.0" +"@aws-sdk/credential-provider-ini@npm:3.236.0": + version: 3.236.0 + resolution: "@aws-sdk/credential-provider-ini@npm:3.236.0" dependencies: "@aws-sdk/credential-provider-env": 3.226.0 "@aws-sdk/credential-provider-imds": 3.226.0 "@aws-sdk/credential-provider-process": 3.226.0 - "@aws-sdk/credential-provider-sso": 3.235.0 + "@aws-sdk/credential-provider-sso": 3.236.0 "@aws-sdk/credential-provider-web-identity": 3.226.0 "@aws-sdk/property-provider": 3.226.0 "@aws-sdk/shared-ini-file-loader": 3.226.0 "@aws-sdk/types": 3.226.0 tslib: ^2.3.1 - checksum: c910746d90ec363691dc99d53594beb49ea8562ef69c5a2e9a1702febc7d870cfe5b0f441afdae2bed643a4e8901b82f965660cdd70077bb006978fdafddeb05 + checksum: e748aba37dab11ed395fa60c7eee119b997e4c64b1a838c93258cfe6a6e289dfa0aa20d292b94d06da8148b7cb32604b24f84aadc2a389bf8248c64d80ef44d5 languageName: node linkType: hard -"@aws-sdk/credential-provider-node@npm:3.235.0, @aws-sdk/credential-provider-node@npm:^3.208.0": - version: 3.235.0 - resolution: "@aws-sdk/credential-provider-node@npm:3.235.0" +"@aws-sdk/credential-provider-node@npm:3.236.0, @aws-sdk/credential-provider-node@npm:^3.208.0": + version: 3.236.0 + resolution: "@aws-sdk/credential-provider-node@npm:3.236.0" dependencies: "@aws-sdk/credential-provider-env": 3.226.0 "@aws-sdk/credential-provider-imds": 3.226.0 - "@aws-sdk/credential-provider-ini": 3.235.0 + "@aws-sdk/credential-provider-ini": 3.236.0 "@aws-sdk/credential-provider-process": 3.226.0 - "@aws-sdk/credential-provider-sso": 3.235.0 + "@aws-sdk/credential-provider-sso": 3.236.0 "@aws-sdk/credential-provider-web-identity": 3.226.0 "@aws-sdk/property-provider": 3.226.0 "@aws-sdk/shared-ini-file-loader": 3.226.0 "@aws-sdk/types": 3.226.0 tslib: ^2.3.1 - checksum: bfea379a5e66c311c57a1f7390b2e508d42f8cf3e1a48a5b9585b33c76339c7e8806dba413c7d5bf6d669ffd05a4c55ef705f92bf4687ba8f5e359c5d3c7495e + checksum: d5a26b40a985edfef5006efde921d58b78829b67eed9fea56e108b99eec437a02721b93ef0e2dcd9a4739716c85d8db75935a71d2afa1190b0f1d0010511f649 languageName: node linkType: hard @@ -785,17 +785,17 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/credential-provider-sso@npm:3.235.0": - version: 3.235.0 - resolution: "@aws-sdk/credential-provider-sso@npm:3.235.0" +"@aws-sdk/credential-provider-sso@npm:3.236.0": + version: 3.236.0 + resolution: "@aws-sdk/credential-provider-sso@npm:3.236.0" dependencies: - "@aws-sdk/client-sso": 3.235.0 + "@aws-sdk/client-sso": 3.236.0 "@aws-sdk/property-provider": 3.226.0 "@aws-sdk/shared-ini-file-loader": 3.226.0 - "@aws-sdk/token-providers": 3.235.0 + "@aws-sdk/token-providers": 3.236.0 "@aws-sdk/types": 3.226.0 tslib: ^2.3.1 - checksum: 358fdda689c1f9be768516ca39de17e7fa7832d3d89d8893dd58f5d3377908ee2580155c1ba984c5ed21a410cda681bd22c7c74876af06b3496641a98b62353f + checksum: 215e0cd08c4dca453a80ce695bf4dddb94ce7562100bfa64e2ba5406ab3aeb6cf92882bd714b6ee95d3f13c45f3286eb616d32e1ffc58728f1e21e7eb171b301 languageName: node linkType: hard @@ -811,25 +811,25 @@ __metadata: linkType: hard "@aws-sdk/credential-providers@npm:^3.208.0": - version: 3.235.0 - resolution: "@aws-sdk/credential-providers@npm:3.235.0" + version: 3.236.0 + resolution: "@aws-sdk/credential-providers@npm:3.236.0" dependencies: - "@aws-sdk/client-cognito-identity": 3.235.0 - "@aws-sdk/client-sso": 3.235.0 - "@aws-sdk/client-sts": 3.235.0 - "@aws-sdk/credential-provider-cognito-identity": 3.235.0 + "@aws-sdk/client-cognito-identity": 3.236.0 + "@aws-sdk/client-sso": 3.236.0 + "@aws-sdk/client-sts": 3.236.0 + "@aws-sdk/credential-provider-cognito-identity": 3.236.0 "@aws-sdk/credential-provider-env": 3.226.0 "@aws-sdk/credential-provider-imds": 3.226.0 - "@aws-sdk/credential-provider-ini": 3.235.0 - "@aws-sdk/credential-provider-node": 3.235.0 + "@aws-sdk/credential-provider-ini": 3.236.0 + "@aws-sdk/credential-provider-node": 3.236.0 "@aws-sdk/credential-provider-process": 3.226.0 - "@aws-sdk/credential-provider-sso": 3.235.0 + "@aws-sdk/credential-provider-sso": 3.236.0 "@aws-sdk/credential-provider-web-identity": 3.226.0 "@aws-sdk/property-provider": 3.226.0 "@aws-sdk/shared-ini-file-loader": 3.226.0 "@aws-sdk/types": 3.226.0 tslib: ^2.3.1 - checksum: 3c8436f78a4ddcf295b06d77ed3cb912a8ae56ee047526663f8316dde6b646095c8288480214c9e3f68cb98772511cb7fcec6f8c4100fba899c495a9429c3ba2 + checksum: 3105661946fa12322b18a2227f26a8b734072850882e771493b398e714b433c024873a4495a18d55a4213022b07c658cb7ed38859b5fa152fc2ce1e02b408ca1 languageName: node linkType: hard @@ -967,8 +967,8 @@ __metadata: linkType: hard "@aws-sdk/lib-storage@npm:^3.208.0": - version: 3.235.0 - resolution: "@aws-sdk/lib-storage@npm:3.235.0" + version: 3.236.0 + resolution: "@aws-sdk/lib-storage@npm:3.236.0" dependencies: "@aws-sdk/middleware-endpoint": 3.226.0 "@aws-sdk/smithy-client": 3.234.0 @@ -979,7 +979,7 @@ __metadata: peerDependencies: "@aws-sdk/abort-controller": ^3.0.0 "@aws-sdk/client-s3": ^3.0.0 - checksum: a9a8993fdc8c05769a2f0c5c838e9b2ba72216f234e67c1a8bcebf8f812ab397d48c8b118959f4d9918ad8811fdf93b3b47812075b0995af8c11cb26c0f3c305 + checksum: 783b67c4605a4f7d4072ce38b2b2416a0a6771784f9ba5b1608be3777e7d21a18c820fff94218856607f984c453376e25e80393ef233365476850e354f8e15f8 languageName: node linkType: hard @@ -1355,16 +1355,16 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/token-providers@npm:3.235.0": - version: 3.235.0 - resolution: "@aws-sdk/token-providers@npm:3.235.0" +"@aws-sdk/token-providers@npm:3.236.0": + version: 3.236.0 + resolution: "@aws-sdk/token-providers@npm:3.236.0" dependencies: - "@aws-sdk/client-sso-oidc": 3.235.0 + "@aws-sdk/client-sso-oidc": 3.236.0 "@aws-sdk/property-provider": 3.226.0 "@aws-sdk/shared-ini-file-loader": 3.226.0 "@aws-sdk/types": 3.226.0 tslib: ^2.3.1 - checksum: 5b9cd8cfd697f04a7c628329af1cc5003ec25fe08626148c55389311b53ecbaa47b83f2c6b832fa847f9db2ca5a3f63bcb8a1059b1b3b3590010acf3d4610896 + checksum: c5e6c88f22d35002f2d575d23ba5a530ee195b7220d7c0c58603bc2fafb6632a6755bc863cb7519eadda110d2f3193a144f34cc0236df285fcde40c980ba653a languageName: node linkType: hard From 56633804ddcfcffe6c2bd324823d84f42f85a188 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Thu, 22 Dec 2022 11:11:28 +0100 Subject: [PATCH 55/94] Gracefully handle client-level (vs. response-level) errors Signed-off-by: Eric Peterson --- .changeset/search-mele-kalikimaka.md | 5 ++++ .../ElasticSearchSearchEngineIndexer.test.ts | 29 +++++++++++++++++++ .../ElasticSearchSearchEngineIndexer.ts | 11 +++++++ 3 files changed, 45 insertions(+) create mode 100644 .changeset/search-mele-kalikimaka.md diff --git a/.changeset/search-mele-kalikimaka.md b/.changeset/search-mele-kalikimaka.md new file mode 100644 index 0000000000..4f1faff02f --- /dev/null +++ b/.changeset/search-mele-kalikimaka.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend-module-elasticsearch': patch +--- + +Fixed a bug that could cause the backstage backend to unexpectedly terminate when client errors were encountered during the indexing process. diff --git a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.test.ts b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.test.ts index 92193976b4..94b59fcf42 100644 --- a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.test.ts +++ b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.test.ts @@ -250,4 +250,33 @@ describe('ElasticSearchSearchEngineIndexer', () => { // Final deletion shouldn't be called. expect(deleteSpy).not.toHaveBeenCalled(); }); + + it('handles bulk client rejection', async () => { + // Given an ES client wrapper that rejects an error + const expectedError = new Error('HTTP Timeout'); + const mockClientWrapper = ElasticSearchClientWrapper.fromClientOptions({ + node: 'http://localhost:9200', + Connection: mock.getConnection(), + }); + mockClientWrapper.bulk = jest.fn().mockRejectedValue(expectedError); + + // And a search engine indexer that uses that client wrapper + indexer = new ElasticSearchSearchEngineIndexer({ + type: 'some-type', + indexPrefix: '', + indexSeparator: '-index__', + alias: 'some-type-index__search', + logger: getVoidLogger(), + elasticSearchClientWrapper: mockClientWrapper, + batchSize: 1000, + }); + + // When the indexer is run in the test pipeline + const { error } = await TestPipeline.fromIndexer(indexer) + .withDocuments([{ title: 'a', location: 'a', text: '/a' }]) + .execute(); + + // Then the pipeline should have received the expected error + expect(error).toBe(expectedError); + }); }); diff --git a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.ts b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.ts index 29f20ea650..3f842976e5 100644 --- a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.ts +++ b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.ts @@ -60,6 +60,7 @@ export class ElasticSearchSearchEngineIndexer extends BatchSearchEngineIndexer { private readonly sourceStream: Readable; private readonly elasticSearchClientWrapper: ElasticSearchClientWrapper; private bulkResult: Promise; + private bulkClientError?: Error; constructor(options: ElasticSearchSearchEngineIndexerOptions) { super({ batchSize: options.batchSize }); @@ -94,6 +95,11 @@ export class ElasticSearchSearchEngineIndexer extends BatchSearchEngineIndexer { }, refreshOnCompletion: that.indexName, }); + + // Safely catch errors thrown by the bulk helper client, e.g. HTTP timeouts + this.bulkResult.catch(e => { + this.bulkClientError = e; + }); } async initialize(): Promise { @@ -197,6 +203,11 @@ export class ElasticSearchSearchEngineIndexer extends BatchSearchEngineIndexer { * backpressure in other parts of the indexing pipeline. */ private isReady(): Promise { + // Early exit if the underlying ES client encountered an error. + if (this.bulkClientError) { + return Promise.reject(this.bulkClientError); + } + return new Promise(resolve => { const interval = setInterval(() => { if (this.received === this.processed) { From ade5b9ca4acedc6c24b0afa51bdd7a211b9185bb Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 22 Dec 2022 11:18:53 +0100 Subject: [PATCH 56/94] chore: updating app-config.yaml for uffizzi Signed-off-by: blam --- .../uffizzi.production.app-config.yaml | 122 ++++++++++++++++-- 1 file changed, 114 insertions(+), 8 deletions(-) diff --git a/.github/uffizzi/uffizzi.production.app-config.yaml b/.github/uffizzi/uffizzi.production.app-config.yaml index c65bc51786..56055c8342 100644 --- a/.github/uffizzi/uffizzi.production.app-config.yaml +++ b/.github/uffizzi/uffizzi.production.app-config.yaml @@ -2,6 +2,9 @@ app: title: Backstage Uffizzi Environment baseUrl: ${UFFIZZI_URL} +organization: + name: My Company + backend: baseUrl: ${UFFIZZI_URL} auth: @@ -11,20 +14,19 @@ backend: listen: port: 7007 database: - client: pg - connection: - host: db - user: ${POSTGRES_USER} - password: ${POSTGRES_PASSWORD} - port: 5432 + client: better-sqlite3 + connection: ':memory:' cache: store: memory cors: - origin: http://localhost:3000 + origin: ${UFFIZZI_URL} methods: [GET, HEAD, PATCH, POST, PUT, DELETE] credentials: true csp: connect-src: ["'self'", 'http:', 'https:'] + # Content-Security-Policy directives follow the Helmet format: https://helmetjs.github.io/#reference + # Default Helmet Content-Security-Policy values can be removed by setting the key to false + auth: environment: production providers: {} @@ -34,6 +36,74 @@ catalog: - type: url target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml +proxy: + '/circleci/api': + target: https://circleci.com/api/v1.1 + headers: + Circle-Token: ${CIRCLECI_AUTH_TOKEN} + + '/jenkins/api': + target: http://localhost:8080 + headers: + Authorization: ${JENKINS_BASIC_AUTH_HEADER} + + '/travisci/api': + target: https://api.travis-ci.com + changeOrigin: true + headers: + Authorization: ${TRAVISCI_AUTH_TOKEN} + travis-api-version: '3' + + '/newrelic/apm/api': + target: https://api.newrelic.com/v2 + headers: + X-Api-Key: ${NEW_RELIC_REST_API_KEY} + + '/newrelic/api': + target: https://api.newrelic.com + headers: + X-Api-Key: ${NEW_RELIC_USER_KEY} + + '/pagerduty': + target: https://api.pagerduty.com + headers: + Authorization: Token token=${PAGERDUTY_TOKEN} + + '/buildkite/api': + target: https://api.buildkite.com/v2/ + headers: + Authorization: ${BUILDKITE_TOKEN} + + '/sentry/api': + target: https://sentry.io/api/ + allowedMethods: ['GET'] + headers: + Authorization: ${SENTRY_TOKEN} + + '/ilert': + target: https://api.ilert.com + allowedMethods: ['GET', 'POST', 'PUT'] + allowedHeaders: ['Authorization'] + headers: + Authorization: ${ILERT_AUTH_HEADER} + + '/airflow': + target: https://your.airflow.instance.com/api/v1 + headers: + Authorization: ${AIRFLOW_BASIC_AUTH_HEADER} + + '/gocd': + target: https://your.gocd.instance.com/go/api + allowedMethods: ['GET'] + allowedHeaders: ['Authorization'] + headers: + Authorization: Basic ${GOCD_AUTH_CREDENTIALS} + + '/dynatrace': + target: https://your.dynatrace.instance.com/api/v2 + headers: + Authorization: 'Api-Token ${DYNATRACE_ACCESS_TOKEN}' + techdocs: builder: 'local' # Alternatives - 'external' generator: @@ -68,6 +138,7 @@ kubernetes: clusterLocatorMethods: - type: 'config' clusters: [] + kafka: clientId: backstage clusters: @@ -75,13 +146,45 @@ kafka: dashboardUrl: https://akhq.io/ brokers: - localhost:9092 + allure: baseUrl: http://localhost:5050/allure-docker-service + integrations: github: - host: github.com token: ${GITHUB_TOKEN} -scaffolder: {} + ### Example for how to add your GitHub Enterprise instance using the API: + # - host: ghe.example.net + # apiBaseUrl: https://ghe.example.net/api/v3 + # token: ${GHE_TOKEN} + ### Example for how to add your GitHub Enterprise instance using raw HTTP fetches (token is optional): + # - host: ghe.example.net + # rawBaseUrl: https://ghe.example.net/raw + # token: ${GHE_TOKEN} + gitlab: + - host: gitlab.com + token: ${GITLAB_TOKEN} + ### Example for how to add a bitbucket cloud integration + # bitbucketCloud: + # - username: ${BITBUCKET_USERNAME} + # appPassword: ${BITBUCKET_APP_PASSWORD} + ### Example for how to add your bitbucket server instance using the API: + # - host: server.bitbucket.com + # apiBaseUrl: server.bitbucket.com + # username: ${BITBUCKET_SERVER_USERNAME} + # appPassword: ${BITBUCKET_SERVER_APP_PASSWORD} + azure: + - host: dev.azure.com + token: ${AZURE_TOKEN} + # googleGcs: + # clientEmail: 'example@example.com' + # privateKey: ${GCS_PRIVATE_KEY} + awsS3: + - host: amazonaws.com + accessKeyId: ${AWS_ACCESS_KEY_ID} + secretAccessKey: ${AWS_SECRET_ACCESS_KEY} + costInsights: engineerCost: 200000 engineerThreshold: 0.5 @@ -151,3 +254,6 @@ apacheAirflow: gocd: baseUrl: https://your.gocd.instance.com + +permission: + enabled: true From 50aef4b5b26ed287b9729d556d444b2eb39539d9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 22 Dec 2022 11:00:14 +0000 Subject: [PATCH 57/94] fix(deps): update dependency eslint to v8.30.0 Signed-off-by: Renovate Bot --- yarn.lock | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/yarn.lock b/yarn.lock index 5562f1fa30..7c130dbe31 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9019,20 +9019,20 @@ __metadata: languageName: node linkType: hard -"@eslint/eslintrc@npm:^1.3.3": - version: 1.3.3 - resolution: "@eslint/eslintrc@npm:1.3.3" +"@eslint/eslintrc@npm:^1.4.0": + version: 1.4.0 + resolution: "@eslint/eslintrc@npm:1.4.0" dependencies: ajv: ^6.12.4 debug: ^4.3.2 espree: ^9.4.0 - globals: ^13.15.0 + globals: ^13.19.0 ignore: ^5.2.0 import-fresh: ^3.2.1 js-yaml: ^4.1.0 minimatch: ^3.1.2 strip-json-comments: ^3.1.1 - checksum: f03e9d6727efd3e0719da2051ea80c0c73d20e28c171121527dbb868cd34232ca9c1d0525a66e517a404afea26624b1e47895b6a92474678418c2f50c9566694 + checksum: 73e39c833deafde8d8706e6fa9b52b6d99927c094ead8e405ea4174e8197ec24aac9ba88ae38cc8ad32eaccf07b9c7fc5dc70761d1fba6da41a928691447305f languageName: node linkType: hard @@ -9853,14 +9853,14 @@ __metadata: languageName: node linkType: hard -"@humanwhocodes/config-array@npm:^0.11.6": - version: 0.11.7 - resolution: "@humanwhocodes/config-array@npm:0.11.7" +"@humanwhocodes/config-array@npm:^0.11.8": + version: 0.11.8 + resolution: "@humanwhocodes/config-array@npm:0.11.8" dependencies: "@humanwhocodes/object-schema": ^1.2.1 debug: ^4.1.1 minimatch: ^3.0.5 - checksum: cf506dc45d9488af7fbf108ea6ac2151ba1a25e6d2b94b9b4fc36d2c1e4099b89ff560296dbfa13947e44604d4ca4a90d97a4fb167370bf8dd01a6ca2b6d83ac + checksum: 0fd6b3c54f1674ce0a224df09b9c2f9846d20b9e54fabae1281ecfc04f2e6ad69bf19e1d6af6a28f88e8aa3990168b6cb9e1ef755868c3256a630605ec2cb1d3 languageName: node linkType: hard @@ -21670,11 +21670,11 @@ __metadata: linkType: hard "eslint@npm:^8.6.0": - version: 8.29.0 - resolution: "eslint@npm:8.29.0" + version: 8.30.0 + resolution: "eslint@npm:8.30.0" dependencies: - "@eslint/eslintrc": ^1.3.3 - "@humanwhocodes/config-array": ^0.11.6 + "@eslint/eslintrc": ^1.4.0 + "@humanwhocodes/config-array": ^0.11.8 "@humanwhocodes/module-importer": ^1.0.1 "@nodelib/fs.walk": ^1.2.8 ajv: ^6.10.0 @@ -21693,7 +21693,7 @@ __metadata: file-entry-cache: ^6.0.1 find-up: ^5.0.0 glob-parent: ^6.0.2 - globals: ^13.15.0 + globals: ^13.19.0 grapheme-splitter: ^1.0.4 ignore: ^5.2.0 import-fresh: ^3.0.0 @@ -21714,7 +21714,7 @@ __metadata: text-table: ^0.2.0 bin: eslint: bin/eslint.js - checksum: e05204b05907b82d910983995cb946e0ba62ca514eb2b6791c43f623333b143564a2eee0139909d31c10935c21877d815b1f76dd674a59cb91c471064325c4ab + checksum: b7525bb465b342665c3b8bab7e114d514ef1bc4e79f211c919863f9c71767e7412ec82383a22614a92d159783f91101018817000f7c61ce69a5e7015280cafaf languageName: node linkType: hard @@ -23516,12 +23516,12 @@ __metadata: languageName: node linkType: hard -"globals@npm:^13.15.0": - version: 13.15.0 - resolution: "globals@npm:13.15.0" +"globals@npm:^13.19.0": + version: 13.19.0 + resolution: "globals@npm:13.19.0" dependencies: type-fest: ^0.20.2 - checksum: 383ade0873b2ab29ce6d143466c203ed960491575bc97406395e5c8434026fb02472ab2dfff5bc16689b8460269b18fda1047975295cd0183904385c51258bae + checksum: a000dbd00bcf28f0941d8a29c3522b1c3b8e4bfe4e60e262c477a550c3cbbe8dbe2925a6905f037acd40f9a93c039242e1f7079c76b0fd184bc41dcc3b5c8e2e languageName: node linkType: hard From 5157208d24b55fa10e70a9cd0809781c5f1ccfe1 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 22 Dec 2022 12:24:06 +0100 Subject: [PATCH 58/94] chore: just build the backend Signed-off-by: blam --- .github/workflows/uffizzi-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/uffizzi-build.yml b/.github/workflows/uffizzi-build.yml index 25306e0f57..a170521ec9 100644 --- a/.github/workflows/uffizzi-build.yml +++ b/.github/workflows/uffizzi-build.yml @@ -41,7 +41,7 @@ jobs: - name: backstage build run: | - yarn build:all + yarn workspace backend build - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 From aa33a0689448a7e98185068f4c30aa7ecd0cc6d1 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Thu, 22 Dec 2022 12:49:09 +0100 Subject: [PATCH 59/94] Optimize indexer throughput Signed-off-by: Eric Peterson --- .changeset/search-pressure-back.md | 5 +++++ .../engines/ElasticSearchSearchEngineIndexer.ts | 15 ++++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 .changeset/search-pressure-back.md diff --git a/.changeset/search-pressure-back.md b/.changeset/search-pressure-back.md new file mode 100644 index 0000000000..3dd36456a7 --- /dev/null +++ b/.changeset/search-pressure-back.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend-module-elasticsearch': patch +--- + +Improved index throughput by optimizing when and how many documents were made available to the bulk client. diff --git a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.ts b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.ts index 3f842976e5..d1534c53a7 100644 --- a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.ts +++ b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.ts @@ -45,7 +45,6 @@ function duration(startTimestamp: [number, number]): string { * @public */ export class ElasticSearchSearchEngineIndexer extends BatchSearchEngineIndexer { - private received: number = 0; private processed: number = 0; private removableIndices: string[] = []; @@ -59,11 +58,13 @@ export class ElasticSearchSearchEngineIndexer extends BatchSearchEngineIndexer { private readonly logger: Logger; private readonly sourceStream: Readable; private readonly elasticSearchClientWrapper: ElasticSearchClientWrapper; + private configuredBatchSize: number; private bulkResult: Promise; private bulkClientError?: Error; constructor(options: ElasticSearchSearchEngineIndexerOptions) { super({ batchSize: options.batchSize }); + this.configuredBatchSize = options.batchSize; this.logger = options.logger.child({ documentType: options.type }); this.startTimestamp = process.hrtime(); this.type = options.type; @@ -121,7 +122,6 @@ export class ElasticSearchSearchEngineIndexer extends BatchSearchEngineIndexer { async index(documents: IndexableDocument[]): Promise { await this.isReady(); documents.forEach(document => { - this.received++; this.sourceStream.push(document); }); } @@ -208,9 +208,18 @@ export class ElasticSearchSearchEngineIndexer extends BatchSearchEngineIndexer { return Promise.reject(this.bulkClientError); } + // Optimization: if the stream that ES reads from has fewer docs queued + // than the configured batch size, continue early to allow more docs to be + // queued + if (this.sourceStream.readableLength < this.configuredBatchSize) { + return Promise.resolve(); + } + + // Otherwise, continue periodically checking the stream queue to see if + // ES has consumed the documents and continue when it's ready for more. return new Promise(resolve => { const interval = setInterval(() => { - if (this.received === this.processed) { + if (this.sourceStream.readableLength < this.configuredBatchSize) { clearInterval(interval); resolve(); } From dcfdaeccd377046f40b4f961a20031d2687620b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 22 Dec 2022 13:39:43 +0100 Subject: [PATCH 60/94] minor improvements to the coverage backend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/old-planets-care.md | 5 +++++ .../src/service/CoverageUtils.test.ts | 6 ++++-- .../src/service/CoverageUtils.ts | 6 ++++-- .../src/service/converter/jacoco.ts | 1 + .../src/service/router.ts | 18 ++++++++++-------- .../src/service/standaloneServer.ts | 16 ++++++++++++++++ 6 files changed, 40 insertions(+), 12 deletions(-) create mode 100644 .changeset/old-planets-care.md diff --git a/.changeset/old-planets-care.md b/.changeset/old-planets-care.md new file mode 100644 index 0000000000..4b30ff7d2d --- /dev/null +++ b/.changeset/old-planets-care.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-code-coverage-backend': patch +--- + +`RouterOptions` now accepts an optional `catalogApi` argument, allowing custom catalog clients to be used. This is leveraged in the local standalone development runner to pass in a catalog client that returns fake data. diff --git a/plugins/code-coverage-backend/src/service/CoverageUtils.test.ts b/plugins/code-coverage-backend/src/service/CoverageUtils.test.ts index e0b4c6589e..44b1411cc4 100644 --- a/plugins/code-coverage-backend/src/service/CoverageUtils.test.ts +++ b/plugins/code-coverage-backend/src/service/CoverageUtils.test.ts @@ -209,7 +209,7 @@ describe('CodeCoverageUtils', () => { } catch (error: any) { err = error; } - expect(err?.message).toEqual('Content-Type missing'); + expect(err?.message).toEqual('Content-Type header missing'); }); it('rejects unsupported content type', () => { @@ -223,7 +223,9 @@ describe('CodeCoverageUtils', () => { } catch (error: any) { err = error; } - expect(err?.message).toEqual('Illegal Content-Type'); + expect(err?.message).toEqual( + 'Content-Type header "application/json" not supported, expected "text/xml" possibly followed by a charset', + ); }); it('parses the body', () => { diff --git a/plugins/code-coverage-backend/src/service/CoverageUtils.ts b/plugins/code-coverage-backend/src/service/CoverageUtils.ts index d965ae6300..a4d3405868 100644 --- a/plugins/code-coverage-backend/src/service/CoverageUtils.ts +++ b/plugins/code-coverage-backend/src/service/CoverageUtils.ts @@ -162,9 +162,11 @@ export class CoverageUtils { validateRequestBody(req: Request) { const contentType = req.headers['content-type']; if (!contentType) { - throw new InputError('Content-Type missing'); + throw new InputError('Content-Type header missing'); } else if (!contentType.match(/^text\/xml($|;)/)) { - throw new InputError('Illegal Content-Type'); + throw new InputError( + `Content-Type header "${contentType}" not supported, expected "text/xml" possibly followed by a charset`, + ); } const body = req.body; if (!body) { diff --git a/plugins/code-coverage-backend/src/service/converter/jacoco.ts b/plugins/code-coverage-backend/src/service/converter/jacoco.ts index f3047d9f00..ac5ccbfa45 100644 --- a/plugins/code-coverage-backend/src/service/converter/jacoco.ts +++ b/plugins/code-coverage-backend/src/service/converter/jacoco.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { BranchHit, FileEntry } from '../types'; import { JacocoSourceFile, JacocoXML } from './types'; import { Logger } from 'winston'; diff --git a/plugins/code-coverage-backend/src/service/router.ts b/plugins/code-coverage-backend/src/service/router.ts index b87b21bffd..4d4fb779b5 100644 --- a/plugins/code-coverage-backend/src/service/router.ts +++ b/plugins/code-coverage-backend/src/service/router.ts @@ -46,6 +46,7 @@ export interface RouterOptions { database: PluginDatabaseManager; urlReader: UrlReader; logger: Logger; + catalogApi?: CatalogApi; } export interface CodeCoverageApi { @@ -59,7 +60,8 @@ export const makeRouter = async ( const codeCoverageDatabase = await CodeCoverageDatabase.create(database); const codecovUrl = await discovery.getExternalBaseUrl('code-coverage'); - const catalogApi: CatalogApi = new CatalogClient({ discoveryApi: discovery }); + const catalogApi = + options.catalogApi ?? new CatalogClient({ discoveryApi: discovery }); const scm = ScmIntegrations.fromConfig(config); const router = Router(); @@ -167,10 +169,10 @@ export const makeRouter = async ( * /report?entity=component:default/mycomponent&coverageType=cobertura */ router.post('/report', async (req, res) => { - const { entity, coverageType } = req.query; - const entityLookup = await catalogApi.getEntityByRef(entity as string); - if (!entityLookup) { - throw new NotFoundError(`No entity found matching ${entity}`); + const { entity: entityRef, coverageType } = req.query; + const entity = await catalogApi.getEntityByRef(entityRef as string); + if (!entity) { + throw new NotFoundError(`No entity found matching ${entityRef}`); } let converter: Converter; @@ -185,7 +187,7 @@ export const makeRouter = async ( } const { sourceLocation, vcs, scmFiles, body } = - await utils.processCoveragePayload(entityLookup, req); + await utils.processCoveragePayload(entity, req); const files = converter.convert(body, scmFiles); if (!files || files.length === 0) { @@ -193,7 +195,7 @@ export const makeRouter = async ( } const coverage = await utils.buildCoverage( - entityLookup, + entity, sourceLocation, vcs, files, @@ -204,7 +206,7 @@ export const makeRouter = async ( links: [ { rel: 'coverage', - href: `${codecovUrl}/report?entity=${entity}`, + href: `${codecovUrl}/report?entity=${entityRef}`, }, ], }); diff --git a/plugins/code-coverage-backend/src/service/standaloneServer.ts b/plugins/code-coverage-backend/src/service/standaloneServer.ts index c4ced6baeb..41493660fb 100644 --- a/plugins/code-coverage-backend/src/service/standaloneServer.ts +++ b/plugins/code-coverage-backend/src/service/standaloneServer.ts @@ -21,6 +21,8 @@ import { UrlReaders, useHotMemoize, } from '@backstage/backend-common'; +import { CatalogApi } from '@backstage/catalog-client'; +import { CompoundEntityRef, parseEntityRef } from '@backstage/catalog-model'; import { Server } from 'http'; import knexFactory from 'knex'; import { Logger } from 'winston'; @@ -52,13 +54,27 @@ export async function startStandaloneServer( return knex; }); + const catalogApi = { + async getEntityByRef(entityRef: string | CompoundEntityRef) { + const { kind, namespace, name } = parseEntityRef(entityRef); + return { + apiVersion: 'backstage.io/v1alpha1', + kind, + metadata: { name, namespace }, + spec: {}, + }; + }, + } as Partial as CatalogApi; + logger.debug('Starting application server...'); + const router = await createRouter({ database: { getClient: async () => db }, config, discovery: SingleHostDiscovery.fromConfig(config), urlReader: UrlReaders.default({ logger, config }), logger, + catalogApi, }); let service = createServiceBuilder(module) From 71ec7585a81bd19ddb288cd341339087b9836e8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 22 Dec 2022 14:07:22 +0100 Subject: [PATCH 61/94] document the metadata.uid field MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .../software-catalog/descriptor-format.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/features/software-catalog/descriptor-format.md b/docs/features/software-catalog/descriptor-format.md index f0151d7618..383fd698f9 100644 --- a/docs/features/software-catalog/descriptor-format.md +++ b/docs/features/software-catalog/descriptor-format.md @@ -251,6 +251,23 @@ the entity belongs to the `"default"` namespace. Namespaces may also be part of the catalog, and are `v1` / `Namespace` entities, i.e. not Backstage specific but the same as in Kubernetes. +### `uid` [output] + +Each entity gets an automatically generated globally unique ID when it first +enters the database. This field is not meant to be specified as input data, but +is rater created by the database engine itself when producing the output entity. + +Note that `uid` values are _not_ to be seen as stable, and should _not_ be used +as external references to an entity. The `uid` can change over time even when a +human observer might think that it wouldn't. As one of many examples, +unregistering and re-registering the exact same file will result in a different +`uid` value even though everything else is the same. Therefore there is very +little, if any, reason to read or use this field externally. + +If you want to refer to an entity by some form of an identifier, you should +always use [string-form entity reference](references.md#string-references) +instead. + ### `title` [optional] A display name of the entity, to be presented in user interfaces instead of the From 1e1a9fe979802f96160e54e0b1bd1271dae156df Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Thu, 22 Dec 2022 14:09:49 +0100 Subject: [PATCH 62/94] Prevent the readiness check from looping endlessly Signed-off-by: Eric Peterson --- .changeset/search-break-loop.md | 5 +++++ .../ElasticSearchSearchEngineIndexer.ts | 20 +++++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 .changeset/search-break-loop.md diff --git a/.changeset/search-break-loop.md b/.changeset/search-break-loop.md new file mode 100644 index 0000000000..ce65645900 --- /dev/null +++ b/.changeset/search-break-loop.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend-module-elasticsearch': patch +--- + +Fixed a bug that could cause an indexing process to silently fail, timeout, and accumulate stale indices. diff --git a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.ts b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.ts index d1534c53a7..a9d15c421e 100644 --- a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.ts +++ b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.ts @@ -217,11 +217,27 @@ export class ElasticSearchSearchEngineIndexer extends BatchSearchEngineIndexer { // Otherwise, continue periodically checking the stream queue to see if // ES has consumed the documents and continue when it's ready for more. - return new Promise(resolve => { + return new Promise((isReady, abort) => { + let streamLengthChecks = 0; const interval = setInterval(() => { + streamLengthChecks++; + if (this.sourceStream.readableLength < this.configuredBatchSize) { clearInterval(interval); - resolve(); + isReady(); + } + + // Do not allow this interval to loop endlessly; anything longer than 5 + // minutes likely indicates an unrecoverable error in ES; direct the + // user to inspect ES logs for more clues and abort in order to allow + // the index to be cleaned up. + if (streamLengthChecks >= 6000) { + clearInterval(interval); + abort( + new Error( + 'Exceeded 5 minutes waiting for elastic to be ready to accept more documents. Check the elastic logs for possible problems.', + ), + ); } }, 50); }); From 9083673db8003db7e2bdae1d6182fbb0ea67c18f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 10 Dec 2022 16:25:36 +0100 Subject: [PATCH 63/94] cli: slight lockfile lib refactor Signed-off-by: Patrik Oldsberg --- packages/cli/src/commands/versions/bump.ts | 2 +- .../cli/src/lib/versioning/Lockfile.test.ts | 4 +- packages/cli/src/lib/versioning/Lockfile.ts | 46 +++++++------------ 3 files changed, 20 insertions(+), 32 deletions(-) diff --git a/packages/cli/src/commands/versions/bump.ts b/packages/cli/src/commands/versions/bump.ts index 37eb858dcf..45b966a52d 100644 --- a/packages/cli/src/commands/versions/bump.ts +++ b/packages/cli/src/commands/versions/bump.ts @@ -201,7 +201,7 @@ export default async (opts: OptionValues) => { lockfile.remove(name, range); } } - await lockfile.save(); + await lockfile.save(lockfilePath); } const breakingUpdates = new Map(); diff --git a/packages/cli/src/lib/versioning/Lockfile.test.ts b/packages/cli/src/lib/versioning/Lockfile.test.ts index e663ec4a93..e2842b4c95 100644 --- a/packages/cli/src/lib/versioning/Lockfile.test.ts +++ b/packages/cli/src/lib/versioning/Lockfile.test.ts @@ -111,7 +111,7 @@ describe('Lockfile', () => { expect(lockfile.toString()).toBe(mockADedup); await expect(fs.readFile('/yarn.lock', 'utf8')).resolves.toBe(mockA); - await expect(lockfile.save()).resolves.toBeUndefined(); + await expect(lockfile.save('/yarn.lock')).resolves.toBeUndefined(); await expect(fs.readFile('/yarn.lock', 'utf8')).resolves.toBe(mockADedup); }); @@ -268,7 +268,7 @@ describe('New Lockfile', () => { expect(lockfile.toString()).toBe(mockANewDedup); await expect(fs.readFile('/yarn.lock', 'utf8')).resolves.toBe(mockANew); - await expect(lockfile.save()).resolves.toBeUndefined(); + await expect(lockfile.save('/yarn.lock')).resolves.toBeUndefined(); await expect(fs.readFile('/yarn.lock', 'utf8')).resolves.toBe( mockANewDedup, ); diff --git a/packages/cli/src/lib/versioning/Lockfile.ts b/packages/cli/src/lib/versioning/Lockfile.ts index 0559ca86a1..faafb465a4 100644 --- a/packages/cli/src/lib/versioning/Lockfile.ts +++ b/packages/cli/src/lib/versioning/Lockfile.ts @@ -65,20 +65,6 @@ type AnalyzeResult = { newRanges: AnalyzeResultNewRange[]; }; -function parseLockfile(lockfileContents: string) { - try { - return { - object: parseSyml(lockfileContents), - type: 'success', - }; - } catch (err) { - return { - object: null, - type: err, - }; - } -} - // the new yarn header is handled out of band of the parsing // https://github.com/yarnpkg/berry/blob/0c5974f193a9397630e9aee2b3876cca62611149/packages/yarnpkg-core/sources/Project.ts#L1741-L1746 const NEW_HEADER = `${[ @@ -86,11 +72,6 @@ const NEW_HEADER = `${[ `# Manual changes might be lost - proceed with caution!\n`, ].join(``)}\n`; -function stringifyLockfile(data: LockfileData, legacy: boolean) { - return legacy - ? legacyStringifyLockfile(data) - : NEW_HEADER + stringifySyml(data); -} // taken from yarn parser package // https://github.com/yarnpkg/berry/blob/0c5974f193a9397630e9aee2b3876cca62611149/packages/yarnpkg-parsers/sources/syml.ts#L136 const LEGACY_REGEX = /^(#.*(\r?\n))*?#\s+yarn\s+lockfile\s+v1\r?\n/i; @@ -111,13 +92,19 @@ const SPECIAL_OBJECT_KEYS = [ export class Lockfile { static async load(path: string) { const lockfileContents = await fs.readFile(path, 'utf8'); - const legacy = LEGACY_REGEX.test(lockfileContents); - const lockfile = parseLockfile(lockfileContents); - if (lockfile.type !== 'success') { - throw new Error(`Failed yarn.lock parse with ${lockfile.type}`); + return Lockfile.parse(lockfileContents); + } + + static parse(content: string) { + const legacy = LEGACY_REGEX.test(content); + + let data: LockfileData; + try { + data = parseSyml(content); + } catch (err) { + throw new Error(`Failed yarn.lock parse, ${err}`); } - const data = lockfile.object as LockfileData; const packages = new Map(); for (const [key, value] of Object.entries(data)) { @@ -144,11 +131,10 @@ export class Lockfile { } } - return new Lockfile(path, packages, data, legacy); + return new Lockfile(packages, data, legacy); } private constructor( - private readonly path: string, private readonly packages: Map, private readonly data: LockfileData, private readonly legacy: boolean = false, @@ -340,11 +326,13 @@ export class Lockfile { } } - async save() { - await fs.writeFile(this.path, this.toString(), 'utf8'); + async save(path: string) { + await fs.writeFile(path, this.toString(), 'utf8'); } toString() { - return stringifyLockfile(this.data, this.legacy); + return this.legacy + ? legacyStringifyLockfile(this.data) + : NEW_HEADER + stringifySyml(this.data); } } From d2ad8ca3af5fc6d4f8c5306e76c0bb930841d4c2 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 12 Dec 2022 18:56:56 +0100 Subject: [PATCH 64/94] cli: add Lockfile.diff Signed-off-by: Patrik Oldsberg --- .../cli/src/lib/versioning/Lockfile.test.ts | 236 +++++++++++++++++- packages/cli/src/lib/versioning/Lockfile.ts | 61 ++++- 2 files changed, 288 insertions(+), 9 deletions(-) diff --git a/packages/cli/src/lib/versioning/Lockfile.test.ts b/packages/cli/src/lib/versioning/Lockfile.test.ts index e2842b4c95..8209ef1f23 100644 --- a/packages/cli/src/lib/versioning/Lockfile.test.ts +++ b/packages/cli/src/lib/versioning/Lockfile.test.ts @@ -78,10 +78,12 @@ describe('Lockfile', () => { }); const lockfile = await Lockfile.load('/yarn.lock'); - expect(lockfile.get('a')).toEqual([{ range: '^1', version: '1.0.1' }]); + expect(lockfile.get('a')).toEqual([ + { range: '^1', version: '1.0.1', dataKey: 'a@^1' }, + ]); expect(lockfile.get('b')).toEqual([ - { range: '2.0.x', version: '2.0.1' }, - { range: '^2', version: '2.0.0' }, + { range: '2.0.x', version: '2.0.1', dataKey: 'b@2.0.x' }, + { range: '^2', version: '2.0.0', dataKey: 'b@^2' }, ]); expect(lockfile.toString()).toBe(mockA); }); @@ -234,11 +236,13 @@ describe('New Lockfile', () => { }); const lockfile = await Lockfile.load('/yarn.lock'); - expect(lockfile.get('a')).toEqual([{ range: '^1', version: '1.0.1' }]); + expect(lockfile.get('a')).toEqual([ + { range: '^1', version: '1.0.1', dataKey: 'a@^1' }, + ]); expect(lockfile.get('b')).toEqual([ - { range: '2.0.x', version: '2.0.1' }, - { range: '^2.0.1', version: '2.0.1' }, - { range: '^2', version: '2.0.0' }, + { range: '2.0.x', version: '2.0.1', dataKey: 'b@2.0.x, b@^2.0.1' }, + { range: '^2.0.1', version: '2.0.1', dataKey: 'b@2.0.x, b@^2.0.1' }, + { range: '^2', version: '2.0.0', dataKey: 'b@^2' }, ]); expect(lockfile.toString()).toBe(mockANew); }); @@ -315,4 +319,222 @@ describe('New Lockfile', () => { mockANewLocalDedup, ); }); + + describe('diff', () => { + const lockfileLegacyA = Lockfile.parse(`${HEADER} +a@^1: + version "1.0.1" + resolved "https://my-registry/a-1.0.01.tgz#abc123" + integrity sha512-xyz + dependencies: + b "^2" + +b@3: + version "3.0.1" + integrity sha512-abc1 + +b@2.0.x: + version "2.0.1" + integrity sha512-abc2 + +b@^2: + version "2.0.0" + integrity sha512-abc3 + +c@^1: + version "1.0.1" + integrity x +`); + + const lockfileLegacyB = Lockfile.parse(`${HEADER} +a@^1: + version "1.0.1" + resolved "https://my-registry/a-1.0.01.tgz#abc123" + integrity sha512-xyz-other + dependencies: + b "^2" + +b@2.0.x, b@^2: + version "2.0.0" + integrity sha512-abc3 + +b@4: + version "4.0.0" + integrity sha512-abc + +d@^1: + version "1.0.1" + integrity x +`); + + const lockfileModernA = Lockfile.parse(`${HEADER} +"a@^1": + version "1.0.1" + resolved "https://my-registry/a-1.0.01.tgz#abc123" + checksum sha512-xyz + dependencies: + b "^2" + +"b@3": + version "3.0.1" + checksum sha512-abc1 + +"b@2.0.x": + version "2.0.1" + checksum sha512-abc2 + +"b@^2": + version "2.0.0" + checksum sha512-abc3 + +"c@^1": + version "1.0.1" + checksum x +`); + + const lockfileModernB = Lockfile.parse(`${HEADER} +"a@^1": + version "1.0.1" + resolution "a@npm:1.0.1" + checksum sha512-xyz-other + dependencies: + b "^2" + +"b@2.0.x, b@^2": + version "2.0.0" + checksum sha512-abc3 + +"b@4": + version "4.0.0" + checksum sha512-abc + +"d@^1": + version "1.0.1" + checksum x +`); + + it('should diff two legacy lockfiles', async () => { + expect(lockfileLegacyA.diff(lockfileLegacyB)).toEqual({ + added: [ + { name: 'b', range: '4' }, + { name: 'd', range: '^1' }, + ], + changed: [ + { name: 'a', range: '^1' }, + { name: 'b', range: '2.0.x' }, + ], + removed: [ + { name: 'b', range: '3' }, + { name: 'c', range: '^1' }, + ], + }); + expect(lockfileLegacyB.diff(lockfileLegacyA)).toEqual({ + added: [ + { name: 'b', range: '3' }, + { name: 'c', range: '^1' }, + ], + changed: [ + { name: 'a', range: '^1' }, + { name: 'b', range: '2.0.x' }, + ], + removed: [ + { name: 'b', range: '4' }, + { name: 'd', range: '^1' }, + ], + }); + }); + + it('should diff two modern lockfiles', async () => { + expect(lockfileModernA.diff(lockfileModernB)).toEqual({ + added: [ + { name: 'b', range: '4' }, + { name: 'd', range: '^1' }, + ], + changed: [ + { name: 'a', range: '^1' }, + { name: 'b', range: '2.0.x' }, + ], + removed: [ + { name: 'b', range: '3' }, + { name: 'c', range: '^1' }, + ], + }); + expect(lockfileModernB.diff(lockfileModernA)).toEqual({ + added: [ + { name: 'b', range: '3' }, + { name: 'c', range: '^1' }, + ], + changed: [ + { name: 'a', range: '^1' }, + { name: 'b', range: '2.0.x' }, + ], + removed: [ + { name: 'b', range: '4' }, + { name: 'd', range: '^1' }, + ], + }); + }); + + it('should diff legacy and modern lockfiles', async () => { + expect(lockfileLegacyA.diff(lockfileModernB)).toEqual({ + added: [ + { name: 'b', range: '4' }, + { name: 'd', range: '^1' }, + ], + changed: [ + { name: 'a', range: '^1' }, + { name: 'b', range: '2.0.x' }, + ], + removed: [ + { name: 'b', range: '3' }, + { name: 'c', range: '^1' }, + ], + }); + expect(lockfileLegacyB.diff(lockfileModernA)).toEqual({ + added: [ + { name: 'b', range: '3' }, + { name: 'c', range: '^1' }, + ], + changed: [ + { name: 'a', range: '^1' }, + { name: 'b', range: '2.0.x' }, + ], + removed: [ + { name: 'b', range: '4' }, + { name: 'd', range: '^1' }, + ], + }); + }); + + it('should diff modern and legacy lockfiles', async () => { + expect(lockfileModernA.diff(lockfileLegacyB)).toEqual({ + added: [ + { name: 'b', range: '4' }, + { name: 'd', range: '^1' }, + ], + changed: [ + { name: 'a', range: '^1' }, + { name: 'b', range: '2.0.x' }, + ], + removed: [ + { name: 'b', range: '3' }, + { name: 'c', range: '^1' }, + ], + }); + expect(lockfileModernB.diff(lockfileLegacyA)).toEqual({ + added: [ + { name: 'b', range: '3' }, + { name: 'c', range: '^1' }, + ], + changed: [ + { name: 'a', range: '^1' }, + { name: 'b', range: '2.0.x' }, + ], + removed: [ + { name: 'b', range: '4' }, + { name: 'd', range: '^1' }, + ], + }); + }); + }); }); diff --git a/packages/cli/src/lib/versioning/Lockfile.ts b/packages/cli/src/lib/versioning/Lockfile.ts index faafb465a4..d156c9498d 100644 --- a/packages/cli/src/lib/versioning/Lockfile.ts +++ b/packages/cli/src/lib/versioning/Lockfile.ts @@ -26,7 +26,8 @@ type LockfileData = { [entry: string]: { version: string; resolved?: string; - integrity?: string; + integrity?: string /* old */; + checksum?: string /* new */; dependencies?: { [name: string]: string }; }; }; @@ -34,6 +35,7 @@ type LockfileData = { type LockfileQueryEntry = { range: string; version: string; + dataKey: string; }; /** Entries that have an invalid version range, for example an npm tag */ @@ -127,7 +129,7 @@ export class Lockfile { if (range.startsWith('npm:')) { range = range.slice('npm:'.length); } - queries.push({ range, version: value.version }); + queries.push({ range, version: value.version, dataKey: key }); } } @@ -326,6 +328,61 @@ export class Lockfile { } } + diff(newLockfile: Lockfile) { + const diff = { + added: new Array<{ name: string; range: string }>(), + removed: new Array<{ name: string; range: string }>(), + changed: new Array<{ name: string; range: string }>(), + }; + + // Keeps track of packages that only exist in the old lockfile + const remainingOldNames = new Set(this.packages.keys()); + + for (const [name, newQueries] of newLockfile.packages) { + remainingOldNames.delete(name); + + const oldQueries = this.packages.get(name); + // If the packages doesn't exist in the old lockfile, add all entries + if (!oldQueries) { + diff.added.push(...newQueries.map(q => ({ name, range: q.range }))); + continue; + } + + const remainingOldRanges = new Set(oldQueries.map(q => q.range)); + + for (const newQuery of newQueries) { + remainingOldRanges.delete(newQuery.range); + + const oldQuery = oldQueries.find(q => q.range === newQuery.range); + if (!oldQuery) { + diff.added.push({ name, range: newQuery.range }); + continue; + } + + const newPkg = newLockfile.data[newQuery.dataKey]; + const oldPkg = this.data[oldQuery.dataKey]; + if (newPkg && oldPkg) { + const oldCheck = oldPkg.integrity || oldPkg.checksum; + const newCheck = newPkg.integrity || newPkg.checksum; + if (!oldCheck || !newCheck || oldCheck !== newCheck) { + diff.changed.push({ name, range: newQuery.range }); + } + } + } + + for (const oldRange of remainingOldRanges) { + diff.removed.push({ name, range: oldRange }); + } + } + + for (const name of remainingOldNames) { + const queries = this.packages.get(name) ?? []; + diff.removed.push(...queries.map(q => ({ name, range: q.range }))); + } + + return diff; + } + async save(path: string) { await fs.writeFile(path, this.toString(), 'utf8'); } From 60196dc40fcc115a65aa98f4bc4038d23262792d Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 13 Dec 2022 22:10:51 +0100 Subject: [PATCH 65/94] cli: flip around lockfile diff Signed-off-by: Patrik Oldsberg --- .../cli/src/lib/versioning/Lockfile.test.ts | 64 +++++++++---------- packages/cli/src/lib/versioning/Lockfile.ts | 63 +++++++++++------- 2 files changed, 71 insertions(+), 56 deletions(-) diff --git a/packages/cli/src/lib/versioning/Lockfile.test.ts b/packages/cli/src/lib/versioning/Lockfile.test.ts index 8209ef1f23..b65d2c0fbe 100644 --- a/packages/cli/src/lib/versioning/Lockfile.test.ts +++ b/packages/cli/src/lib/versioning/Lockfile.test.ts @@ -416,30 +416,30 @@ d@^1: it('should diff two legacy lockfiles', async () => { expect(lockfileLegacyA.diff(lockfileLegacyB)).toEqual({ added: [ - { name: 'b', range: '4' }, - { name: 'd', range: '^1' }, + { name: 'b', range: '3' }, + { name: 'c', range: '^1' }, ], changed: [ { name: 'a', range: '^1' }, { name: 'b', range: '2.0.x' }, ], removed: [ - { name: 'b', range: '3' }, - { name: 'c', range: '^1' }, + { name: 'b', range: '4' }, + { name: 'd', range: '^1' }, ], }); expect(lockfileLegacyB.diff(lockfileLegacyA)).toEqual({ added: [ - { name: 'b', range: '3' }, - { name: 'c', range: '^1' }, + { name: 'b', range: '4' }, + { name: 'd', range: '^1' }, ], changed: [ { name: 'a', range: '^1' }, { name: 'b', range: '2.0.x' }, ], removed: [ - { name: 'b', range: '4' }, - { name: 'd', range: '^1' }, + { name: 'b', range: '3' }, + { name: 'c', range: '^1' }, ], }); }); @@ -447,30 +447,30 @@ d@^1: it('should diff two modern lockfiles', async () => { expect(lockfileModernA.diff(lockfileModernB)).toEqual({ added: [ - { name: 'b', range: '4' }, - { name: 'd', range: '^1' }, + { name: 'b', range: '3' }, + { name: 'c', range: '^1' }, ], changed: [ { name: 'a', range: '^1' }, { name: 'b', range: '2.0.x' }, ], removed: [ - { name: 'b', range: '3' }, - { name: 'c', range: '^1' }, + { name: 'b', range: '4' }, + { name: 'd', range: '^1' }, ], }); expect(lockfileModernB.diff(lockfileModernA)).toEqual({ added: [ - { name: 'b', range: '3' }, - { name: 'c', range: '^1' }, + { name: 'b', range: '4' }, + { name: 'd', range: '^1' }, ], changed: [ { name: 'a', range: '^1' }, { name: 'b', range: '2.0.x' }, ], removed: [ - { name: 'b', range: '4' }, - { name: 'd', range: '^1' }, + { name: 'b', range: '3' }, + { name: 'c', range: '^1' }, ], }); }); @@ -478,30 +478,30 @@ d@^1: it('should diff legacy and modern lockfiles', async () => { expect(lockfileLegacyA.diff(lockfileModernB)).toEqual({ added: [ - { name: 'b', range: '4' }, - { name: 'd', range: '^1' }, + { name: 'b', range: '3' }, + { name: 'c', range: '^1' }, ], changed: [ { name: 'a', range: '^1' }, { name: 'b', range: '2.0.x' }, ], removed: [ - { name: 'b', range: '3' }, - { name: 'c', range: '^1' }, + { name: 'b', range: '4' }, + { name: 'd', range: '^1' }, ], }); expect(lockfileLegacyB.diff(lockfileModernA)).toEqual({ added: [ - { name: 'b', range: '3' }, - { name: 'c', range: '^1' }, + { name: 'b', range: '4' }, + { name: 'd', range: '^1' }, ], changed: [ { name: 'a', range: '^1' }, { name: 'b', range: '2.0.x' }, ], removed: [ - { name: 'b', range: '4' }, - { name: 'd', range: '^1' }, + { name: 'b', range: '3' }, + { name: 'c', range: '^1' }, ], }); }); @@ -509,30 +509,30 @@ d@^1: it('should diff modern and legacy lockfiles', async () => { expect(lockfileModernA.diff(lockfileLegacyB)).toEqual({ added: [ - { name: 'b', range: '4' }, - { name: 'd', range: '^1' }, + { name: 'b', range: '3' }, + { name: 'c', range: '^1' }, ], changed: [ { name: 'a', range: '^1' }, { name: 'b', range: '2.0.x' }, ], removed: [ - { name: 'b', range: '3' }, - { name: 'c', range: '^1' }, + { name: 'b', range: '4' }, + { name: 'd', range: '^1' }, ], }); expect(lockfileModernB.diff(lockfileLegacyA)).toEqual({ added: [ - { name: 'b', range: '3' }, - { name: 'c', range: '^1' }, + { name: 'b', range: '4' }, + { name: 'd', range: '^1' }, ], changed: [ { name: 'a', range: '^1' }, { name: 'b', range: '2.0.x' }, ], removed: [ - { name: 'b', range: '4' }, - { name: 'd', range: '^1' }, + { name: 'b', range: '3' }, + { name: 'c', range: '^1' }, ], }); }); diff --git a/packages/cli/src/lib/versioning/Lockfile.ts b/packages/cli/src/lib/versioning/Lockfile.ts index d156c9498d..8f4bd6af19 100644 --- a/packages/cli/src/lib/versioning/Lockfile.ts +++ b/packages/cli/src/lib/versioning/Lockfile.ts @@ -38,6 +38,17 @@ type LockfileQueryEntry = { dataKey: string; }; +type LockfileDiffEntry = { + name: string; + range: string; +}; + +type LockfileDiff = { + added: LockfileDiffEntry[]; + changed: LockfileDiffEntry[]; + removed: LockfileDiffEntry[]; +}; + /** Entries that have an invalid version range, for example an npm tag */ type AnalyzeResultInvalidRange = { name: string; @@ -328,56 +339,60 @@ export class Lockfile { } } - diff(newLockfile: Lockfile) { + /** + * Diff with another lockfile, returning entries that have been + * added, changed, and removed compared to the other lockfile. + */ + diff(otherLockfile: Lockfile): LockfileDiff { const diff = { added: new Array<{ name: string; range: string }>(), - removed: new Array<{ name: string; range: string }>(), changed: new Array<{ name: string; range: string }>(), + removed: new Array<{ name: string; range: string }>(), }; - // Keeps track of packages that only exist in the old lockfile + // Keeps track of packages that only exist in this lockfile const remainingOldNames = new Set(this.packages.keys()); - for (const [name, newQueries] of newLockfile.packages) { + for (const [name, otherQueries] of otherLockfile.packages) { remainingOldNames.delete(name); - const oldQueries = this.packages.get(name); - // If the packages doesn't exist in the old lockfile, add all entries - if (!oldQueries) { - diff.added.push(...newQueries.map(q => ({ name, range: q.range }))); + const thisQueries = this.packages.get(name); + // If the packages doesn't exist in this lockfile, add all entries + if (!thisQueries) { + diff.removed.push(...otherQueries.map(q => ({ name, range: q.range }))); continue; } - const remainingOldRanges = new Set(oldQueries.map(q => q.range)); + const remainingOldRanges = new Set(thisQueries.map(q => q.range)); - for (const newQuery of newQueries) { - remainingOldRanges.delete(newQuery.range); + for (const otherQuery of otherQueries) { + remainingOldRanges.delete(otherQuery.range); - const oldQuery = oldQueries.find(q => q.range === newQuery.range); - if (!oldQuery) { - diff.added.push({ name, range: newQuery.range }); + const thisQuery = thisQueries.find(q => q.range === otherQuery.range); + if (!thisQuery) { + diff.removed.push({ name, range: otherQuery.range }); continue; } - const newPkg = newLockfile.data[newQuery.dataKey]; - const oldPkg = this.data[oldQuery.dataKey]; - if (newPkg && oldPkg) { - const oldCheck = oldPkg.integrity || oldPkg.checksum; - const newCheck = newPkg.integrity || newPkg.checksum; - if (!oldCheck || !newCheck || oldCheck !== newCheck) { - diff.changed.push({ name, range: newQuery.range }); + const otherPkg = otherLockfile.data[otherQuery.dataKey]; + const thisPkg = this.data[thisQuery.dataKey]; + if (otherPkg && thisPkg) { + const thisCheck = thisPkg.integrity || thisPkg.checksum; + const otherCheck = otherPkg.integrity || otherPkg.checksum; + if (!thisCheck || !otherCheck || thisCheck !== otherCheck) { + diff.changed.push({ name, range: otherQuery.range }); } } } - for (const oldRange of remainingOldRanges) { - diff.removed.push({ name, range: oldRange }); + for (const thisRange of remainingOldRanges) { + diff.added.push({ name, range: thisRange }); } } for (const name of remainingOldNames) { const queries = this.packages.get(name) ?? []; - diff.removed.push(...queries.map(q => ({ name, range: q.range }))); + diff.added.push(...queries.map(q => ({ name, range: q.range }))); } return diff; From 3c71bf7789e198370028c575aa08ae1e03661ad3 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 13 Dec 2022 22:26:32 +0100 Subject: [PATCH 66/94] cli: fix lockfile diff for entries without checksum Signed-off-by: Patrik Oldsberg --- .../cli/src/lib/versioning/Lockfile.test.ts | 49 +++++++++++++++++++ packages/cli/src/lib/versioning/Lockfile.ts | 2 +- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/lib/versioning/Lockfile.test.ts b/packages/cli/src/lib/versioning/Lockfile.test.ts index b65d2c0fbe..7b0da9ce8c 100644 --- a/packages/cli/src/lib/versioning/Lockfile.test.ts +++ b/packages/cli/src/lib/versioning/Lockfile.test.ts @@ -536,5 +536,54 @@ d@^1: ], }); }); + + it('should handle workspace ranges', async () => { + const lockfile = `${HEADER} +"@backstage/app-defaults@workspace:^, @backstage/app-defaults@workspace:packages/app-defaults": + version: 0.0.0-use.local + resolution: "@backstage/app-defaults@workspace:packages/app-defaults" + dependencies: + "@backstage/cli": "workspace:^" + "@backstage/core-app-api": "workspace:^" + "@backstage/core-components": "workspace:^" + "@backstage/core-plugin-api": "workspace:^" + "@backstage/plugin-permission-react": "workspace:^" + "@backstage/test-utils": "workspace:^" + "@backstage/theme": "workspace:^" + "@material-ui/core": ^4.12.2 + "@material-ui/icons": ^4.9.1 + "@testing-library/jest-dom": ^5.10.1 + "@testing-library/react": ^12.1.3 + "@types/node": ^16.11.26 + "@types/react": ^16.13.1 || ^17.0.0 + peerDependencies: + react: ^16.13.1 || ^17.0.0 + react-dom: ^16.13.1 || ^17.0.0 + react-router-dom: 6.0.0-beta.0 || ^6.3.0 + languageName: unknown + linkType: soft + +"@backstage/backend-app-api@workspace:^, @backstage/backend-app-api@workspace:packages/backend-app-api": + version: 0.0.0-use.local + resolution: "@backstage/backend-app-api@workspace:packages/backend-app-api" + dependencies: + "@backstage/backend-common": "workspace:^" + "@backstage/backend-plugin-api": "workspace:^" + "@backstage/backend-tasks": "workspace:^" + "@backstage/cli": "workspace:^" + "@backstage/errors": "workspace:^" + "@backstage/plugin-permission-node": "workspace:^" + express: ^4.17.1 + express-promise-router: ^4.1.0 + winston: ^3.2.1 + languageName: unknown + linkType: soft +`; + expect(Lockfile.parse(lockfile).diff(Lockfile.parse(lockfile))).toEqual({ + added: [], + changed: [], + removed: [], + }); + }); }); }); diff --git a/packages/cli/src/lib/versioning/Lockfile.ts b/packages/cli/src/lib/versioning/Lockfile.ts index 8f4bd6af19..622aac36ae 100644 --- a/packages/cli/src/lib/versioning/Lockfile.ts +++ b/packages/cli/src/lib/versioning/Lockfile.ts @@ -379,7 +379,7 @@ export class Lockfile { if (otherPkg && thisPkg) { const thisCheck = thisPkg.integrity || thisPkg.checksum; const otherCheck = otherPkg.integrity || otherPkg.checksum; - if (!thisCheck || !otherCheck || thisCheck !== otherCheck) { + if (thisCheck !== otherCheck) { diff.changed.push({ name, range: otherQuery.range }); } } From 18543ed94b1caa5194839c63e2fe52f72068877e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 13 Dec 2022 23:21:36 +0100 Subject: [PATCH 67/94] cli: fix lockfile test formats Signed-off-by: Patrik Oldsberg --- .../cli/src/lib/versioning/Lockfile.test.ts | 102 +++++++++--------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/packages/cli/src/lib/versioning/Lockfile.test.ts b/packages/cli/src/lib/versioning/Lockfile.test.ts index 7b0da9ce8c..30c6f6ffee 100644 --- a/packages/cli/src/lib/versioning/Lockfile.test.ts +++ b/packages/cli/src/lib/versioning/Lockfile.test.ts @@ -19,12 +19,20 @@ import mockFs from 'mock-fs'; import { ExtendedPackage } from '../monorepo'; import { Lockfile } from './Lockfile'; -const HEADER = `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +const LEGACY_HEADER = `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. # yarn lockfile v1 `; -const mockA = `${HEADER} +const MODERN_HEADER = `# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 6 + cacheKey: 8 +`; + +const mockA = `${LEGACY_HEADER} a@^1: version "1.0.1" resolved "https://my-registry/a-1.0.01.tgz#abc123" @@ -39,7 +47,7 @@ b@^2: version "2.0.0" `; -const mockADedup = `${HEADER} +const mockADedup = `${LEGACY_HEADER} a@^1: version "1.0.1" resolved "https://my-registry/a-1.0.01.tgz#abc123" @@ -51,7 +59,7 @@ b@2.0.x, b@^2: version "2.0.1" `; -const mockB = `${HEADER} +const mockB = `${LEGACY_HEADER} "@s/a@*", "@s/a@1 || 2", "@s/a@^1": version "1.0.1" @@ -59,7 +67,7 @@ const mockB = `${HEADER} version "2.0.0" `; -const mockBDedup = `${HEADER} +const mockBDedup = `${LEGACY_HEADER} "@s/a@*", "@s/a@1 || 2", "@s/a@^2.0.x": version "2.0.0" @@ -157,15 +165,7 @@ describe('Lockfile', () => { }); }); -const newHeader = `# This file is generated by running "yarn install" inside your project. -# Manual changes might be lost - proceed with caution! - -__metadata: - version: 6 - cacheKey: 8 -`; - -const mockANew = `${newHeader} +const mockANew = `${MODERN_HEADER} a@^1: version: 1.0.1 dependencies: @@ -180,7 +180,7 @@ b@^2: version: 2.0.0 `; -const mockANewDedup = `${newHeader} +const mockANewDedup = `${MODERN_HEADER} a@^1: version: 1.0.1 dependencies: @@ -321,7 +321,7 @@ describe('New Lockfile', () => { }); describe('diff', () => { - const lockfileLegacyA = Lockfile.parse(`${HEADER} + const lockfileLegacyA = Lockfile.parse(`${LEGACY_HEADER} a@^1: version "1.0.1" resolved "https://my-registry/a-1.0.01.tgz#abc123" @@ -346,7 +346,7 @@ c@^1: integrity x `); - const lockfileLegacyB = Lockfile.parse(`${HEADER} + const lockfileLegacyB = Lockfile.parse(`${LEGACY_HEADER} a@^1: version "1.0.1" resolved "https://my-registry/a-1.0.01.tgz#abc123" @@ -367,50 +367,50 @@ d@^1: integrity x `); - const lockfileModernA = Lockfile.parse(`${HEADER} -"a@^1": - version "1.0.1" - resolved "https://my-registry/a-1.0.01.tgz#abc123" - checksum sha512-xyz + const lockfileModernA = Lockfile.parse(`${MODERN_HEADER} +"a@npm:^1": + version: "1.0.1" + resolved: "https://my-registry/a-1.0.01.tgz#abc123" + checksum: sha512-xyz dependencies: - b "^2" + b: "^2" -"b@3": - version "3.0.1" - checksum sha512-abc1 +"b@npm:3": + version: "3.0.1" + checksum: sha512-abc1 -"b@2.0.x": - version "2.0.1" - checksum sha512-abc2 +"b@npm:2.0.x": + version: "2.0.1" + checksum: sha512-abc2 -"b@^2": - version "2.0.0" - checksum sha512-abc3 +"b@npm:^2": + version: "2.0.0" + checksum: sha512-abc3 -"c@^1": - version "1.0.1" - checksum x +"c@npm:^1": + version: "1.0.1" + checksum: x `); - const lockfileModernB = Lockfile.parse(`${HEADER} -"a@^1": - version "1.0.1" - resolution "a@npm:1.0.1" - checksum sha512-xyz-other + const lockfileModernB = Lockfile.parse(`${MODERN_HEADER} +"a@npm:^1": + version: "1.0.1" + resolution: "a@npm:1.0.1" + checksum: sha512-xyz-other dependencies: - b "^2" + b: "^2" -"b@2.0.x, b@^2": - version "2.0.0" - checksum sha512-abc3 +"b@npm:2.0.x, b@npm:^2": + version: "2.0.0" + checksum: sha512-abc3 -"b@4": - version "4.0.0" - checksum sha512-abc +"b@npm:4": + version: "4.0.0" + checksum: sha512-abc -"d@^1": - version "1.0.1" - checksum x +"d@npm:^1": + version: "1.0.1" + checksum: x `); it('should diff two legacy lockfiles', async () => { @@ -538,7 +538,7 @@ d@^1: }); it('should handle workspace ranges', async () => { - const lockfile = `${HEADER} + const lockfile = `${MODERN_HEADER} "@backstage/app-defaults@workspace:^, @backstage/app-defaults@workspace:packages/app-defaults": version: 0.0.0-use.local resolution: "@backstage/app-defaults@workspace:packages/app-defaults" From 99898258cd30e5dfa0ab2311cfc247cb35ce5756 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 13 Dec 2022 23:21:53 +0100 Subject: [PATCH 68/94] cli: add simple dependency graph utility for lockfiles Signed-off-by: Patrik Oldsberg --- .../cli/src/lib/versioning/Lockfile.test.ts | 197 ++++++++++++++++++ packages/cli/src/lib/versioning/Lockfile.ts | 20 ++ 2 files changed, 217 insertions(+) diff --git a/packages/cli/src/lib/versioning/Lockfile.test.ts b/packages/cli/src/lib/versioning/Lockfile.test.ts index 30c6f6ffee..a60b71be85 100644 --- a/packages/cli/src/lib/versioning/Lockfile.test.ts +++ b/packages/cli/src/lib/versioning/Lockfile.test.ts @@ -586,4 +586,201 @@ d@^1: }); }); }); + + describe('createSimplifiedDependencyGraph', () => { + it('for modern lockfile', () => { + expect( + Lockfile.parse( + `${MODERN_HEADER} +"@backstage/app-defaults@workspace:^, @backstage/app-defaults@workspace:packages/app-defaults": + version: 0.0.0-use.local + resolution: "@backstage/app-defaults@workspace:packages/app-defaults" + dependencies: + "@backstage/cli": "workspace:^" + "@backstage/core-app-api": "workspace:^" + "@backstage/core-components": "workspace:^" + "@backstage/core-plugin-api": "workspace:^" + "@backstage/plugin-permission-react": "workspace:^" + "@backstage/test-utils": "workspace:^" + "@backstage/theme": "workspace:^" + "@material-ui/core": ^4.12.2 + "@material-ui/icons": ^4.9.1 + "@testing-library/jest-dom": ^5.10.1 + "@testing-library/react": ^12.1.3 + "@types/node": ^16.11.26 + "@types/react": ^16.13.1 || ^17.0.0 + peerDependencies: + react: ^16.13.1 || ^17.0.0 + react-dom: ^16.13.1 || ^17.0.0 + react-router-dom: 6.0.0-beta.0 || ^6.3.0 + languageName: unknown + linkType: soft + +"@backstage/backend-app-api@workspace:^, @backstage/backend-app-api@workspace:packages/backend-app-api": + version: 0.0.0-use.local + resolution: "@backstage/backend-app-api@workspace:packages/backend-app-api" + dependencies: + "@backstage/backend-common": "workspace:^" + "@backstage/backend-plugin-api": "workspace:^" + "@backstage/backend-tasks": "workspace:^" + "@backstage/cli": "workspace:^" + "@backstage/errors": "workspace:^" + "@backstage/plugin-permission-node": "workspace:^" + express: ^4.17.1 + express-promise-router: ^4.1.0 + winston: ^3.2.1 + languageName: unknown + linkType: soft +`, + ).createSimplifiedDependencyGraph(), + ).toEqual( + new Map([ + [ + '@backstage/app-defaults', + new Set([ + '@backstage/cli', + '@backstage/core-app-api', + '@backstage/core-components', + '@backstage/core-plugin-api', + '@backstage/plugin-permission-react', + '@backstage/test-utils', + '@backstage/theme', + '@material-ui/core', + '@material-ui/icons', + '@testing-library/jest-dom', + '@testing-library/react', + '@types/node', + '@types/react', + 'react', + 'react-dom', + 'react-router-dom', + ]), + ], + [ + '@backstage/backend-app-api', + new Set([ + '@backstage/backend-common', + '@backstage/backend-plugin-api', + '@backstage/backend-tasks', + '@backstage/cli', + '@backstage/errors', + '@backstage/plugin-permission-node', + 'express', + 'express-promise-router', + 'winston', + ]), + ], + ]), + ); + }); + + it('for simple lockfile without dependencies', () => { + expect( + Lockfile.parse( + `${MODERN_HEADER} +"a@npm:^1": + version: "1.0.1" + +"b@npm:3": + version: "3.0.1" + +"b@npm:2.0.x": + version: "2.0.1" + checksum: sha512-abc2 +`, + ).createSimplifiedDependencyGraph(), + ).toEqual( + new Map([ + ['a', new Set()], + ['b', new Set()], + ]), + ); + }); + + it('for lockfile with dependencies', () => { + expect( + Lockfile.parse( + `${MODERN_HEADER} +"a@npm:^1": + version: "1.0.1" + dependencies: + b: "^2" + +"b@npm:3": + version: "3.0.1" + checksum: sha512-abc1 + +"b@npm:2.0.x": + version: "2.0.1" + checksum: sha512-abc2 + dependencies: + c: "^1" + +"b@npm:^2": + version: "2.0.0" + checksum: sha512-abc3 + peerDependencies: + d: "^1" + +"c@npm:^1": + version: "1.0.1" + +"d@npm:^1": + version: "1.0.2" +`, + ).createSimplifiedDependencyGraph(), + ).toEqual( + new Map([ + ['a', new Set(['b'])], + ['b', new Set(['c', 'd'])], + ['c', new Set()], + ['d', new Set()], + ]), + ); + }); + + it('for legacy lockfile', () => { + expect( + Lockfile.parse( + `${LEGACY_HEADER} +a@^1: + version "1.0.1" + dependencies: + b "^2" + +b@3: + version "3.0.1" + integrity sha512-abc1 + +b@2.0.x: + version "2.0.1" + integrity sha512-abc2 + dependencies: + c "^1" + +b@^2: + version "2.0.0" + integrity sha512-abc3 + dependencies: + d "^1" + +c@^1: + version "1.0.1" + integrity x + +d@^1: + version "1.0.1" + integrity x +`, + ).createSimplifiedDependencyGraph(), + ).toEqual( + new Map([ + ['a', new Set(['b'])], + ['b', new Set(['c', 'd'])], + ['c', new Set()], + ['d', new Set()], + ]), + ); + }); + }); }); diff --git a/packages/cli/src/lib/versioning/Lockfile.ts b/packages/cli/src/lib/versioning/Lockfile.ts index 622aac36ae..4eadaf55f9 100644 --- a/packages/cli/src/lib/versioning/Lockfile.ts +++ b/packages/cli/src/lib/versioning/Lockfile.ts @@ -29,6 +29,7 @@ type LockfileData = { integrity?: string /* old */; checksum?: string /* new */; dependencies?: { [name: string]: string }; + peerDependencies?: { [name: string]: string }; }; }; @@ -339,6 +340,25 @@ export class Lockfile { } } + createSimplifiedDependencyGraph(): Map> { + const graph = new Map>(); + + for (const [name, entries] of this.packages) { + const dependencies = new Set( + entries.flatMap(e => { + const data = this.data[e.dataKey]; + return [ + ...Object.keys(data?.dependencies ?? {}), + ...Object.keys(data?.peerDependencies ?? {}), + ]; + }), + ); + graph.set(name, dependencies); + } + + return graph; + } + /** * Diff with another lockfile, returning entries that have been * added, changed, and removed compared to the other lockfile. From a11a8082ce2dd19539ad5f0f89997bc197037699 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 13 Dec 2022 23:22:44 +0100 Subject: [PATCH 69/94] cli: add git utility for reading a file at a ref Signed-off-by: Patrik Oldsberg --- packages/cli/src/lib/git.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/cli/src/lib/git.ts b/packages/cli/src/lib/git.ts index 074260b38f..c0b50f5140 100644 --- a/packages/cli/src/lib/git.ts +++ b/packages/cli/src/lib/git.ts @@ -64,3 +64,23 @@ export async function listChangedFiles(ref: string) { return Array.from(new Set([...tracked, ...untracked])); } + +/** + * Returns the contents of a file at a specific ref. + */ +export async function readFileAtRef(path: string, ref: string) { + let showRef = ref; + try { + const [base] = await runGit('merge-base', 'HEAD', ref); + showRef = base; + } catch { + // silently fall back to using the ref directly if merge base is not available + } + + const { stdout } = await execFile('git', ['show', `${showRef}:${path}`], { + shell: true, + cwd: paths.targetRoot, + maxBuffer: 1024 * 1024 * 50, + }); + return stdout; +} From 79bc82828ff1e1ff0ce9c855d8a1a48caeb46555 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 13 Dec 2022 23:23:11 +0100 Subject: [PATCH 70/94] cli: add ability to analyze lockfile changes when listing changed packages Signed-off-by: Patrik Oldsberg --- packages/cli/src/lib/monorepo/PackageGraph.ts | 70 ++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/lib/monorepo/PackageGraph.ts b/packages/cli/src/lib/monorepo/PackageGraph.ts index c65f5c003c..8aa7fc9416 100644 --- a/packages/cli/src/lib/monorepo/PackageGraph.ts +++ b/packages/cli/src/lib/monorepo/PackageGraph.ts @@ -18,7 +18,8 @@ import path from 'path'; import { getPackages, Package } from '@manypkg/get-packages'; import { paths } from '../paths'; import { PackageRole } from '../role'; -import { listChangedFiles } from '../git'; +import { listChangedFiles, readFileAtRef } from '../git'; +import { Lockfile } from '../versioning'; type PackageJSON = Package['packageJson']; @@ -191,7 +192,10 @@ export class PackageGraph extends Map { return targets; } - async listChangedPackages(options: { ref: string }) { + async listChangedPackages(options: { + ref: string; + analyzeLockfile?: boolean; + }) { const changedFiles = await listChangedFiles(options.ref); const dirMap = new Map( @@ -234,6 +238,68 @@ export class PackageGraph extends Map { } } + if (changedFiles.includes('yarn.lock') && options.analyzeLockfile) { + // Load the lockfile in the working tree and the one at the ref and diff them + const thisLockfile = await Lockfile.load( + paths.resolveTargetRoot('yarn.lock'), + ); + const otherLockfile = Lockfile.parse( + await readFileAtRef('yarn.lock', options.ref), + ); + const diff = thisLockfile.diff(otherLockfile); + + // Create a simplified dependency graph only keeps track of package names + const graph = thisLockfile.createSimplifiedDependencyGraph(); + + // Merge the dependency graph from the other lockfile into this one in + // order to be able to detect removals accurately. + { + const otherGraph = thisLockfile.createSimplifiedDependencyGraph(); + for (const [name, dependencies] of otherGraph) { + const node = graph.get(name); + if (node) { + dependencies.forEach(d => node.add(d)); + } else { + graph.set(name, dependencies); + } + } + } + + // The check is simplified by only considering the package names rather + // than the exact version range queries that were changed. + // TODO(Rugvip): Use a more exact check + const changedPackages = new Set( + [...diff.added, ...diff.changed, ...diff.removed].map(e => e.name), + ); + + // Starting with our set of changed packages from the diff, we loop through + // the full graph and add any package that has a dependency on a changed package. + // We keep looping until all transitive dependencies have been detected. + let changed = false; + do { + changed = false; + for (const [name, dependencies] of graph) { + if (changedPackages.has(name)) { + continue; + } + for (const dep of dependencies) { + if (changedPackages.has(dep)) { + changed = true; + changedPackages.add(name); + break; + } + } + } + } while (changed); + + // Add all local packages that had a transitive dependency change to the result set + for (const node of this.values()) { + if (changedPackages.has(node.name) && !result.includes(node)) { + result.push(node); + } + } + } + return result; } } From 7c8a974515c75e5e18dcd58523f35f6ec76385fb Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 13 Dec 2022 23:32:30 +0100 Subject: [PATCH 71/94] cli: make --since flag for repo build, lint and test commands diff yarn lock Signed-off-by: Patrik Oldsberg --- .changeset/tasty-impalas-cross.md | 5 +++++ packages/cli/src/commands/repo/build.ts | 1 + packages/cli/src/commands/repo/lint.ts | 5 ++++- packages/cli/src/commands/repo/test.ts | 1 + 4 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 .changeset/tasty-impalas-cross.md diff --git a/.changeset/tasty-impalas-cross.md b/.changeset/tasty-impalas-cross.md new file mode 100644 index 0000000000..f7d7712b29 --- /dev/null +++ b/.changeset/tasty-impalas-cross.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +The `repo test`, `repo lint`, and `repo build` commands will now analyze `yarn.lock` for dependency changes when searching for changed packages. This allows you to use the `--since ` flag even if you have `yarn.lock` changes. diff --git a/packages/cli/src/commands/repo/build.ts b/packages/cli/src/commands/repo/build.ts index 21b1580cb9..a6bf87da82 100644 --- a/packages/cli/src/commands/repo/build.ts +++ b/packages/cli/src/commands/repo/build.ts @@ -84,6 +84,7 @@ export async function command(opts: OptionValues, cmd: Command): Promise { const graph = PackageGraph.fromPackages(packages); const changedPackages = await graph.listChangedPackages({ ref: opts.since, + analyzeLockfile: true, }); const withDevDependents = graph.collectPackageNames( changedPackages.map(pkg => pkg.name), diff --git a/packages/cli/src/commands/repo/lint.ts b/packages/cli/src/commands/repo/lint.ts index 61d85b3773..8e8ec20ff8 100644 --- a/packages/cli/src/commands/repo/lint.ts +++ b/packages/cli/src/commands/repo/lint.ts @@ -34,7 +34,10 @@ export async function command(opts: OptionValues): Promise { if (opts.since) { const graph = PackageGraph.fromPackages(packages); - packages = await graph.listChangedPackages({ ref: opts.since }); + packages = await graph.listChangedPackages({ + ref: opts.since, + analyzeLockfile: true, + }); } // Packages are ordered from most to least number of dependencies, as a diff --git a/packages/cli/src/commands/repo/test.ts b/packages/cli/src/commands/repo/test.ts index 578cb15e83..95bfe8f449 100644 --- a/packages/cli/src/commands/repo/test.ts +++ b/packages/cli/src/commands/repo/test.ts @@ -93,6 +93,7 @@ export async function command(opts: OptionValues, cmd: Command): Promise { const graph = PackageGraph.fromPackages(packages); const changedPackages = await graph.listChangedPackages({ ref: opts.since, + analyzeLockfile: true, }); const packageNames = Array.from( From 536d3e764af41a4600982d0a96200fd0549af26d Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 13 Dec 2022 23:45:08 +0100 Subject: [PATCH 72/94] cli: fix missing arg for lockfile.save() Signed-off-by: Patrik Oldsberg --- packages/cli/src/commands/versions/lint.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/commands/versions/lint.ts b/packages/cli/src/commands/versions/lint.ts index e6bc119b71..8de38548a9 100644 --- a/packages/cli/src/commands/versions/lint.ts +++ b/packages/cli/src/commands/versions/lint.ts @@ -53,7 +53,8 @@ export default async (cmd: OptionValues) => { let success = true; - const lockfile = await Lockfile.load(paths.resolveTargetRoot('yarn.lock')); + const lockfilePath = paths.resolveTargetRoot('yarn.lock'); + const lockfile = await Lockfile.load(lockfilePath); const result = lockfile.analyze({ filter: includedFilter, localPackages: PackageGraph.fromPackages( @@ -69,7 +70,7 @@ export default async (cmd: OptionValues) => { if (fix) { lockfile.replaceVersions(result.newVersions); - await lockfile.save(); + await lockfile.save(lockfilePath); } else { const [newVersionsForbidden, newVersionsAllowed] = partition( result.newVersions, From 1d0304de1b09ffb010d7546882e2daa2525b3e86 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 13 Dec 2022 23:45:31 +0100 Subject: [PATCH 73/94] workflows/ci: remove yarn.lock check and always use --since Signed-off-by: Patrik Oldsberg --- .github/workflows/ci.yml | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a37ae745c4..c4af6021b2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -185,21 +185,10 @@ jobs: with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} - - name: check for yarn.lock changes - id: yarn-lock - run: git diff --quiet origin/master HEAD -- yarn.lock - continue-on-error: true - - name: lint changed packages - if: ${{ steps.yarn-lock.outcome == 'success' }} run: yarn backstage-cli repo lint --since origin/master - - name: lint all packages - if: ${{ steps.yarn-lock.outcome == 'failure' }} - run: yarn backstage-cli repo lint - - name: test changed packages - if: ${{ steps.yarn-lock.outcome == 'success' }} run: yarn backstage-cli repo test --maxWorkers=2 --workerIdleMemoryLimit=1300M --since origin/master env: BACKSTAGE_TEST_DISABLE_DOCKER: 1 @@ -207,17 +196,6 @@ jobs: BACKSTAGE_TEST_DATABASE_POSTGRES9_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres9.ports[5432] }} BACKSTAGE_TEST_DATABASE_MYSQL8_CONNECTION_STRING: mysql://root:root@localhost:${{ job.services.mysql8.ports[3306] }}/ignored - - name: test all packages (and upload coverage) - if: ${{ steps.yarn-lock.outcome == 'failure' }} - run: | - yarn backstage-cli repo test --maxWorkers=2 --workerIdleMemoryLimit=800M --coverage - bash <(curl -s https://codecov.io/bash) -N $(git rev-parse FETCH_HEAD) - env: - BACKSTAGE_TEST_DISABLE_DOCKER: 1 - BACKSTAGE_TEST_DATABASE_POSTGRES13_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres13.ports[5432] }} - BACKSTAGE_TEST_DATABASE_POSTGRES9_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres9.ports[5432] }} - BACKSTAGE_TEST_DATABASE_MYSQL8_CONNECTION_STRING: mysql://root:root@localhost:${{ job.services.mysql8.ports[3306] }}/ignored - - name: ensure clean working directory run: | if files=$(git ls-files --exclude-standard --others --modified) && [[ -z "$files" ]]; then From 7bd873ad15bc66644fd7fb5e590722129473e285 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 14 Dec 2022 00:05:50 +0100 Subject: [PATCH 74/94] cli: handle failure to read lockfile during analysis Signed-off-by: Patrik Oldsberg --- packages/cli/src/lib/monorepo/PackageGraph.ts | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/packages/cli/src/lib/monorepo/PackageGraph.ts b/packages/cli/src/lib/monorepo/PackageGraph.ts index 8aa7fc9416..a1fc95e669 100644 --- a/packages/cli/src/lib/monorepo/PackageGraph.ts +++ b/packages/cli/src/lib/monorepo/PackageGraph.ts @@ -240,12 +240,21 @@ export class PackageGraph extends Map { if (changedFiles.includes('yarn.lock') && options.analyzeLockfile) { // Load the lockfile in the working tree and the one at the ref and diff them - const thisLockfile = await Lockfile.load( - paths.resolveTargetRoot('yarn.lock'), - ); - const otherLockfile = Lockfile.parse( - await readFileAtRef('yarn.lock', options.ref), - ); + let thisLockfile: Lockfile; + let otherLockfile: Lockfile; + try { + thisLockfile = await Lockfile.load( + paths.resolveTargetRoot('yarn.lock'), + ); + otherLockfile = Lockfile.parse( + await readFileAtRef('yarn.lock', options.ref), + ); + } catch (error) { + console.warn( + `Failed to read lockfiles, assuming all packages have changed, ${error}`, + ); + return Array.from(this.values()); + } const diff = thisLockfile.diff(otherLockfile); // Create a simplified dependency graph only keeps track of package names From 39d192d016d8b4a2772137ba518cbab86ad91a45 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 20 Dec 2022 16:48:47 +0100 Subject: [PATCH 75/94] cli: added PackageGraph test for lockfile analysis Signed-off-by: Patrik Oldsberg --- .../cli/src/lib/monorepo/PackageGraph.test.ts | 58 ++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/lib/monorepo/PackageGraph.test.ts b/packages/cli/src/lib/monorepo/PackageGraph.test.ts index feb1709bf7..d8edff9c81 100644 --- a/packages/cli/src/lib/monorepo/PackageGraph.test.ts +++ b/packages/cli/src/lib/monorepo/PackageGraph.test.ts @@ -14,19 +14,25 @@ * limitations under the License. */ +import { resolve as resolvePath } from 'path'; import { getPackages } from '@manypkg/get-packages'; import { PackageGraph } from './PackageGraph'; -import { listChangedFiles } from '../git'; +import { Lockfile } from '../versioning/Lockfile'; +import { listChangedFiles, readFileAtRef } from '../git'; jest.mock('../git'); const mockListChangedFiles = listChangedFiles as jest.MockedFunction< typeof listChangedFiles >; +const mockReadFileAtRef = readFileAtRef as jest.MockedFunction< + typeof readFileAtRef +>; jest.mock('../paths', () => ({ paths: { targetRoot: '/', + resolveTargetRoot: (...paths: string[]) => resolvePath('/', ...paths), }, })); @@ -177,4 +183,54 @@ describe('PackageGraph', () => { graph.listChangedPackages({ ref: 'origin/master' }), ).resolves.toEqual([graph.get('a'), graph.get('b')]); }); + + it('lists changed packages with lockfile analysis', async () => { + const graph = PackageGraph.fromPackages(testPackages); + + mockListChangedFiles.mockResolvedValueOnce( + ['README.md', 'packages/a/src/foo.ts', 'yarn.lock'].sort(), + ); + mockReadFileAtRef.mockResolvedValueOnce(` +a@^1: + version: "1.0.0" + +c@^1: + version: "1.0.0" + dependencies: + c-dep: ^1 + +c-dep@^2: + version: "2.0.0" + integrity: sha512-xyz +`); + jest.spyOn(Lockfile, 'load').mockResolvedValueOnce( + Lockfile.parse(` +a@^1: + version: "1.0.0" + +c@^1: + version: "1.0.0" + dependencies: + c-dep: ^1 + +c-dep@^2: + version: "2.0.0" + integrity: sha512-xyz-other +`), + ); + + await expect( + graph + .listChangedPackages({ + ref: 'origin/master', + analyzeLockfile: true, + }) + .then(pkgs => pkgs.map(pkg => pkg.name)), + ).resolves.toEqual(['a', 'c']); + + expect(mockReadFileAtRef).toHaveBeenCalledWith( + 'yarn.lock', + 'origin/master', + ); + }); }); From ea91c01cc9638538f6861fe0bbe23abfde0b2417 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 22 Dec 2022 14:54:08 +0100 Subject: [PATCH 76/94] cli: fix merge conflicts in Lockfile tests Signed-off-by: Patrik Oldsberg --- packages/cli/src/lib/versioning/Lockfile.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/lib/versioning/Lockfile.test.ts b/packages/cli/src/lib/versioning/Lockfile.test.ts index a60b71be85..0275c5ee50 100644 --- a/packages/cli/src/lib/versioning/Lockfile.test.ts +++ b/packages/cli/src/lib/versioning/Lockfile.test.ts @@ -195,7 +195,7 @@ b@^2: version: 2.0.1 `; -const mockANewLocal = `${newHeader} +const mockANewLocal = `${MODERN_HEADER} a@^1: version: 1.0.1 dependencies: @@ -210,7 +210,7 @@ b@^2: version: 2.0.0 `; -const mockANewLocalDedup = `${newHeader} +const mockANewLocalDedup = `${MODERN_HEADER} a@^1: version: 1.0.1 dependencies: @@ -314,7 +314,7 @@ describe('New Lockfile', () => { await expect(fs.readFile('/yarn.lock', 'utf8')).resolves.toBe( mockANewLocal, ); - await expect(lockfile.save()).resolves.toBeUndefined(); + await expect(lockfile.save('/yarn.lock')).resolves.toBeUndefined(); await expect(fs.readFile('/yarn.lock', 'utf8')).resolves.toBe( mockANewLocalDedup, ); From d756a38b17bf54d4cc79e4173b7b87ce8abc8fd0 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 22 Dec 2022 14:57:07 +0100 Subject: [PATCH 77/94] chore: don't need full types here Signed-off-by: blam --- .github/workflows/uffizzi-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/uffizzi-build.yml b/.github/workflows/uffizzi-build.yml index a170521ec9..e609aa1baa 100644 --- a/.github/workflows/uffizzi-build.yml +++ b/.github/workflows/uffizzi-build.yml @@ -37,7 +37,7 @@ jobs: - name: typescript build run: | - yarn tsc:full + yarn tsc - name: backstage build run: | From 2e026ac169849f9910519c43b4cde387f80c6d52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 22 Dec 2022 15:10:33 +0100 Subject: [PATCH 78/94] api report MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- plugins/code-coverage-backend/api-report.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/code-coverage-backend/api-report.md b/plugins/code-coverage-backend/api-report.md index c33874064b..440af50714 100644 --- a/plugins/code-coverage-backend/api-report.md +++ b/plugins/code-coverage-backend/api-report.md @@ -3,6 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { CatalogApi } from '@backstage/catalog-client'; import { Config } from '@backstage/config'; import express from 'express'; import { Logger } from 'winston'; @@ -15,6 +16,8 @@ export function createRouter(options: RouterOptions): Promise; // @public export interface RouterOptions { + // (undocumented) + catalogApi?: CatalogApi; // (undocumented) config: Config; // (undocumented) From 71a0dee0b80f98fbda5a3b029eca88c009dc88f0 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 22 Dec 2022 16:00:41 +0100 Subject: [PATCH 79/94] chore: build example backend Signed-off-by: blam --- .github/workflows/uffizzi-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/uffizzi-build.yml b/.github/workflows/uffizzi-build.yml index e609aa1baa..1a86f8de71 100644 --- a/.github/workflows/uffizzi-build.yml +++ b/.github/workflows/uffizzi-build.yml @@ -41,7 +41,7 @@ jobs: - name: backstage build run: | - yarn workspace backend build + yarn workspace example-backend build - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 From 48df0692b90d475a68d231310898eafc3dacaf44 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Thu, 22 Dec 2022 16:16:09 +0100 Subject: [PATCH 80/94] repo-tools/chore: Update tests to pass on windows Signed-off-by: Johan Haals --- packages/repo-tools/src/lib/paths.test.ts | 30 +++++++++++------------ 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/repo-tools/src/lib/paths.test.ts b/packages/repo-tools/src/lib/paths.test.ts index c642900612..7c75507a1d 100644 --- a/packages/repo-tools/src/lib/paths.test.ts +++ b/packages/repo-tools/src/lib/paths.test.ts @@ -15,7 +15,7 @@ */ import mockFs from 'mock-fs'; -import { resolve as resolvePath } from 'path'; +import { resolve as resolvePath, join } from 'path'; import { resolvePackagePath, paths, findPackageDirs } from './paths'; describe('paths', () => { @@ -61,15 +61,15 @@ describe('paths', () => { }); it('should return the path to the package if it exists and has a package.json', async () => { expect(await resolvePackagePath('packages/package-a')).toBe( - 'packages/package-a', + join('packages', 'package-a'), ); expect(await resolvePackagePath('packages/package-b')).toBe( - 'packages/package-b', + join('packages', 'package-b'), ); }); it('should work with absolute paths', async () => { expect(await resolvePackagePath('/root/packages/package-a')).toBe( - 'packages/package-a', + join('packages', 'package-a'), ); }); it('should return undefined if the pat is not a directory', async () => { @@ -79,19 +79,19 @@ describe('paths', () => { describe('findPackageDirs', () => { it('should return only the given packages', async () => { expect(await findPackageDirs(['packages/package-a'])).toEqual([ - 'packages/package-a', + join('packages', 'package-a'), ]); }); it('should return only the given packages when using glob patterns', async () => { expect(await findPackageDirs(['packages/*'])).toEqual([ - 'packages/package-a', - 'packages/package-b', + join('packages', 'package-a'), + join('packages', 'package-b'), ]); expect(await findPackageDirs(['packages/*', 'plugins/*'])).toEqual([ - 'packages/package-a', - 'packages/package-b', - 'plugins/plugin-a', - 'plugins/plugin-b', + join('packages', 'package-a'), + join('packages', 'package-b'), + join('plugins', 'plugin-a'), + join('plugins', 'plugin-b'), ]); }); it('should return only the given packages when using absolute paths', async () => { @@ -100,15 +100,15 @@ describe('paths', () => { '/root/packages/package-a', '/root/plugins/plugin-b', ]), - ).toEqual(['packages/package-a', 'plugins/plugin-b']); + ).toEqual([join('packages', 'package-a'), join('plugins', 'plugin-b')]); }); it('should return only the given packages when using absolute paths with glob patterns', async () => { expect( await findPackageDirs(['/root/packages/*', '/root/plugins/*-a']), ).toEqual([ - 'packages/package-a', - 'packages/package-b', - 'plugins/plugin-a', + join('packages', 'package-a'), + join('packages', 'package-b'), + join('plugins', 'plugin-a'), ]); }); }); From 7bd0f3dda7a60563405261d55262525ba8cc97b1 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Thu, 22 Dec 2022 16:31:28 +0100 Subject: [PATCH 81/94] chore: rename to joinPath Signed-off-by: Johan Haals --- packages/repo-tools/src/lib/paths.test.ts | 33 ++++++++++++----------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/packages/repo-tools/src/lib/paths.test.ts b/packages/repo-tools/src/lib/paths.test.ts index 7c75507a1d..5d8dc6ce18 100644 --- a/packages/repo-tools/src/lib/paths.test.ts +++ b/packages/repo-tools/src/lib/paths.test.ts @@ -15,7 +15,7 @@ */ import mockFs from 'mock-fs'; -import { resolve as resolvePath, join } from 'path'; +import { resolve as resolvePath, join as joinPath } from 'path'; import { resolvePackagePath, paths, findPackageDirs } from './paths'; describe('paths', () => { @@ -61,15 +61,15 @@ describe('paths', () => { }); it('should return the path to the package if it exists and has a package.json', async () => { expect(await resolvePackagePath('packages/package-a')).toBe( - join('packages', 'package-a'), + joinPath('packages', 'package-a'), ); expect(await resolvePackagePath('packages/package-b')).toBe( - join('packages', 'package-b'), + joinPath('packages', 'package-b'), ); }); it('should work with absolute paths', async () => { expect(await resolvePackagePath('/root/packages/package-a')).toBe( - join('packages', 'package-a'), + joinPath('packages', 'package-a'), ); }); it('should return undefined if the pat is not a directory', async () => { @@ -79,19 +79,19 @@ describe('paths', () => { describe('findPackageDirs', () => { it('should return only the given packages', async () => { expect(await findPackageDirs(['packages/package-a'])).toEqual([ - join('packages', 'package-a'), + joinPath('packages', 'package-a'), ]); }); it('should return only the given packages when using glob patterns', async () => { expect(await findPackageDirs(['packages/*'])).toEqual([ - join('packages', 'package-a'), - join('packages', 'package-b'), + joinPath('packages', 'package-a'), + joinPath('packages', 'package-b'), ]); expect(await findPackageDirs(['packages/*', 'plugins/*'])).toEqual([ - join('packages', 'package-a'), - join('packages', 'package-b'), - join('plugins', 'plugin-a'), - join('plugins', 'plugin-b'), + joinPath('packages', 'package-a'), + joinPath('packages', 'package-b'), + joinPath('plugins', 'plugin-a'), + joinPath('plugins', 'plugin-b'), ]); }); it('should return only the given packages when using absolute paths', async () => { @@ -100,15 +100,18 @@ describe('paths', () => { '/root/packages/package-a', '/root/plugins/plugin-b', ]), - ).toEqual([join('packages', 'package-a'), join('plugins', 'plugin-b')]); + ).toEqual([ + joinPath('packages', 'package-a'), + joinPath('plugins', 'plugin-b'), + ]); }); it('should return only the given packages when using absolute paths with glob patterns', async () => { expect( await findPackageDirs(['/root/packages/*', '/root/plugins/*-a']), ).toEqual([ - join('packages', 'package-a'), - join('packages', 'package-b'), - join('plugins', 'plugin-a'), + joinPath('packages', 'package-a'), + joinPath('packages', 'package-b'), + joinPath('plugins', 'plugin-a'), ]); }); }); From 8a79b2ef5d95c85b25c45d2c9591bfea325a9902 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Thu, 22 Dec 2022 16:31:59 +0100 Subject: [PATCH 82/94] Use path.join to fix windows tests Signed-off-by: Johan Haals --- .../cli/src/lib/new/factories/webLibraryPackage.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/lib/new/factories/webLibraryPackage.test.ts b/packages/cli/src/lib/new/factories/webLibraryPackage.test.ts index 03be32dbaa..50fef44a67 100644 --- a/packages/cli/src/lib/new/factories/webLibraryPackage.test.ts +++ b/packages/cli/src/lib/new/factories/webLibraryPackage.test.ts @@ -16,7 +16,7 @@ import fs from 'fs-extra'; import mockFs from 'mock-fs'; -import { resolve as resolvePath } from 'path'; +import { resolve as resolvePath, join as joinPath } from 'path'; import { paths } from '../../paths'; import { Task } from '../../tasks'; import { FactoryRegistry } from '../FactoryRegistry'; @@ -73,7 +73,7 @@ describe('webLibraryPackage factory', () => { '', `Creating web-library package ${expectedwebLibraryPackageName}`, 'Checking Prerequisites:', - `availability packages/${expectedwebLibraryPackageName}`, + `availability ${joinPath('packages', expectedwebLibraryPackageName)}`, 'creating temp dir', 'Executing Template:', 'copying .eslintrc.js', @@ -82,7 +82,7 @@ describe('webLibraryPackage factory', () => { 'templating index.ts.hbs', 'copying setupTests.ts', 'Installing:', - `moving packages/${expectedwebLibraryPackageName}`, + `moving ${joinPath('packages', expectedwebLibraryPackageName)}`, ]); await expect( From 93cff3053ea2fa50b455d0f00118b40a770a497e Mon Sep 17 00:00:00 2001 From: Juan Pablo Garcia Ripa Date: Thu, 22 Dec 2022 17:23:25 +0100 Subject: [PATCH 83/94] moved to peerDeps to match version w/api-extractor Signed-off-by: Juan Pablo Garcia Ripa --- .changeset/good-geckos-reflect.md | 5 +++++ packages/repo-tools/package.json | 6 +++--- yarn.lock | 10 +++++----- 3 files changed, 13 insertions(+), 8 deletions(-) create mode 100644 .changeset/good-geckos-reflect.md diff --git a/.changeset/good-geckos-reflect.md b/.changeset/good-geckos-reflect.md new file mode 100644 index 0000000000..315675a3b4 --- /dev/null +++ b/.changeset/good-geckos-reflect.md @@ -0,0 +1,5 @@ +--- +'@backstage/repo-tools': patch +--- + +Move some dependencies as `peerDependencies` because we need to always use same version as in `api-extractor` diff --git a/packages/repo-tools/package.json b/packages/repo-tools/package.json index 09d5fe03f8..2d0c1d240b 100644 --- a/packages/repo-tools/package.json +++ b/packages/repo-tools/package.json @@ -35,9 +35,6 @@ "@manypkg/get-packages": "^1.1.3", "@microsoft/api-documenter": "^7.17.11", "@microsoft/api-extractor": "^7.23.0", - "@microsoft/api-extractor-model": "^7.17.2", - "@microsoft/tsdoc": "0.14.1", - "@microsoft/tsdoc-config": "0.16.2", "chalk": "^4.0.0", "commander": "^9.1.0", "fs-extra": "10.1.0", @@ -53,6 +50,9 @@ "mock-fs": "^5.1.0" }, "peerDependencies": { + "@microsoft/api-extractor-model": "*", + "@microsoft/tsdoc": "*", + "@microsoft/tsdoc-config": "*", "@rushstack/node-core-library": "*", "prettier": "^2.8.1", "typescript": "> 3.0.0" diff --git a/yarn.lock b/yarn.lock index 712c6ce4be..59ce3d0b64 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8225,9 +8225,6 @@ __metadata: "@manypkg/get-packages": ^1.1.3 "@microsoft/api-documenter": ^7.17.11 "@microsoft/api-extractor": ^7.23.0 - "@microsoft/api-extractor-model": ^7.17.2 - "@microsoft/tsdoc": 0.14.1 - "@microsoft/tsdoc-config": 0.16.2 "@types/is-glob": ^4.0.2 "@types/mock-fs": ^4.13.0 chalk: ^4.0.0 @@ -8239,6 +8236,9 @@ __metadata: mock-fs: ^5.1.0 ts-node: ^10.0.0 peerDependencies: + "@microsoft/api-extractor-model": "*" + "@microsoft/tsdoc": "*" + "@microsoft/tsdoc-config": "*" "@rushstack/node-core-library": "*" prettier: ^2.8.1 typescript: "> 3.0.0" @@ -11229,7 +11229,7 @@ __metadata: languageName: node linkType: hard -"@microsoft/api-extractor-model@npm:7.17.2, @microsoft/api-extractor-model@npm:^7.17.2": +"@microsoft/api-extractor-model@npm:7.17.2": version: 7.17.2 resolution: "@microsoft/api-extractor-model@npm:7.17.2" dependencies: @@ -11269,7 +11269,7 @@ __metadata: languageName: node linkType: hard -"@microsoft/tsdoc-config@npm:0.16.2, @microsoft/tsdoc-config@npm:~0.16.1": +"@microsoft/tsdoc-config@npm:~0.16.1": version: 0.16.2 resolution: "@microsoft/tsdoc-config@npm:0.16.2" dependencies: From b8e9ff548ae1a82e2722faa7390b14df523fcc44 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 23 Dec 2022 09:16:04 +0000 Subject: [PATCH 84/94] fix(deps): update dependency @rjsf/utils to v5.0.0-beta.15 Signed-off-by: Renovate Bot --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index dcef77d984..6667da4eb9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12470,8 +12470,8 @@ __metadata: linkType: hard "@rjsf/utils@npm:^5.0.0-beta.14": - version: 5.0.0-beta.14 - resolution: "@rjsf/utils@npm:5.0.0-beta.14" + version: 5.0.0-beta.15 + resolution: "@rjsf/utils@npm:5.0.0-beta.15" dependencies: json-schema-merge-allof: ^0.8.1 jsonpointer: ^5.0.1 @@ -12480,7 +12480,7 @@ __metadata: react-is: ^18.2.0 peerDependencies: react: ^16.14.0 || >=17 - checksum: 66d29377c98f6e778ec0b167734a949528b0ca2bfb4776dea74fd70add1fb339e14ec878b9162a152e83804bc8094c3ebcd9ebf8b1b68a63c6ee2bf4548938c8 + checksum: e22b1cc13183d6a6a57b900bb80825eff1c4680edadb554adb6737c21237c7862b2a249e5b67c0c6971094b5d21224daf108ad1f400286be924bbc5c29784a00 languageName: node linkType: hard From ad8f37bfd98528b59b995012dc0ddf754a75b408 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 23 Dec 2022 10:02:47 +0000 Subject: [PATCH 85/94] fix(deps): update dependency @rjsf/validator-ajv6 to v5.0.0-beta.15 Signed-off-by: Renovate Bot --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 6667da4eb9..6b285136f3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12485,15 +12485,15 @@ __metadata: linkType: hard "@rjsf/validator-ajv6@npm:^5.0.0-beta.14": - version: 5.0.0-beta.14 - resolution: "@rjsf/validator-ajv6@npm:5.0.0-beta.14" + version: 5.0.0-beta.15 + resolution: "@rjsf/validator-ajv6@npm:5.0.0-beta.15" dependencies: ajv: ^6.7.0 lodash: ^4.17.15 lodash-es: ^4.17.15 peerDependencies: "@rjsf/utils": ^5.0.0-beta.1 - checksum: 57b5c6968475a66762f644f2832aad077f8dde0ca9e71c8b0b27c06174855b7e6330f44fa6078fc0f347b728a4a5fd62399378fad26ada3cde32da89bddf6f9f + checksum: ce6d2cd110eee21e37ae08e4081b0f0fd65ac22f8c94ceeaff9f9a9482072897c3f988de1bc770c25031932117e7f71dda0bd166c2bb953af420d0ce1fd255d0 languageName: node linkType: hard From 16c135350a00644def978aa071b137a33b16dc71 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 23 Dec 2022 10:48:21 +0000 Subject: [PATCH 86/94] chore(deps): update dependency canvas to v2.11.0 Signed-off-by: Renovate Bot --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 6b285136f3..b7e826b276 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17866,14 +17866,14 @@ __metadata: linkType: hard "canvas@npm:^2.10.2": - version: 2.10.2 - resolution: "canvas@npm:2.10.2" + version: 2.11.0 + resolution: "canvas@npm:2.11.0" dependencies: "@mapbox/node-pre-gyp": ^1.0.0 nan: ^2.17.0 node-gyp: latest simple-get: ^3.0.3 - checksum: b2e3eb4c3635fa2f67857619621c3d314f935a9e51904536dadf4908ab580dff4f5bcbaafe6eb0255247fa027ca494d5cd97c33376a49a0f994997263fa9944b + checksum: a69a6e0c90014a1b02e52c4c38a627d1a01ffe9539047bec84105cb3554907a947cf39b4a333be43fc1583dd142b641bb5480a4e23f59c6098618c33bf78f67f languageName: node linkType: hard From a4c04c17ebdc0d7bfb8d98bcba68fbb6ef699671 Mon Sep 17 00:00:00 2001 From: Marko Simon Date: Fri, 23 Dec 2022 12:26:31 +0100 Subject: [PATCH 87/94] add nullchecks for uptimePercentage Signed-off-by: Marko Simon --- plugins/ilert/src/components/ServicesPage/ServicesTable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ilert/src/components/ServicesPage/ServicesTable.tsx b/plugins/ilert/src/components/ServicesPage/ServicesTable.tsx index 30a83e4cec..2597986031 100644 --- a/plugins/ilert/src/components/ServicesPage/ServicesTable.tsx +++ b/plugins/ilert/src/components/ServicesPage/ServicesTable.tsx @@ -86,7 +86,7 @@ export const ServicesTable = ({ cellStyle: smColumnStyle, headerStyle: smColumnStyle, render: rowData => ( - {rowData.uptime.uptimePercentage.p90} + {rowData.uptime?.uptimePercentage?.p90 || ''} ), }; const actionsColumn: TableColumn = { From b1279d396dcad51e780bc758b86bc3e8a5b4d489 Mon Sep 17 00:00:00 2001 From: Marko Simon Date: Fri, 23 Dec 2022 12:31:55 +0100 Subject: [PATCH 88/94] add changeset Signed-off-by: Marko Simon --- .changeset/strong-eyes-march.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/strong-eyes-march.md diff --git a/.changeset/strong-eyes-march.md b/.changeset/strong-eyes-march.md new file mode 100644 index 0000000000..4f3bbacfc8 --- /dev/null +++ b/.changeset/strong-eyes-march.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-ilert': patch +--- + +fixed error on service page not showing if historical uptime was disabled on a service From 223e2c5f0328aa86140f815d592fd67c0e95aa09 Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Thu, 22 Dec 2022 21:21:33 +0000 Subject: [PATCH 89/94] add onChange handler to stepper Signed-off-by: Paul Cowan --- .changeset/metal-hotels-deliver.md | 5 ++++ .../TemplateWizardPage/Stepper/Stepper.tsx | 26 +++++++------------ 2 files changed, 15 insertions(+), 16 deletions(-) create mode 100644 .changeset/metal-hotels-deliver.md diff --git a/.changeset/metal-hotels-deliver.md b/.changeset/metal-hotels-deliver.md new file mode 100644 index 0000000000..f2ce925bc2 --- /dev/null +++ b/.changeset/metal-hotels-deliver.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +'add onChange handler`to`Stepper` component diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx index 3d5cb1bfd2..d59fb7f9c1 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx @@ -26,9 +26,9 @@ import { Button, makeStyles, } from '@material-ui/core'; -import { withTheme } from '@rjsf/core-v5'; +import { type IChangeEvent, withTheme } from '@rjsf/core-v5'; import { ErrorSchema, FieldValidation } from '@rjsf/utils'; -import React, { useMemo, useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { NextFieldExtensionOptions } from '../../../extensions'; import { TemplateParameterSchema } from '../../../types'; import { createAsyncValidators } from './createAsyncValidators'; @@ -36,7 +36,6 @@ import { useTemplateSchema } from './useTemplateSchema'; import { ReviewState } from './ReviewState'; import validator from '@rjsf/validator-ajv6'; import { selectedTemplateRouteRef } from '../../../routes'; -import { getDefaultFormState } from '@rjsf/utils'; import { useFormData } from './useFormData'; import { FormProps } from '../../types'; @@ -102,6 +101,11 @@ export const Stepper = (props: StepperProps) => { setActiveStep(prevActiveStep => prevActiveStep - 1); }; + const handleChange = useCallback( + (e: IChangeEvent) => setFormState(e.formData), + [setFormState], + ); + const handleNext = async ({ formData, }: { @@ -111,18 +115,7 @@ export const Stepper = (props: StepperProps) => { // to display it's own loading? Or should we grey out the entire form. setErrors(undefined); - const schema = steps[activeStep]?.schema; - const rootSchema = steps[activeStep]?.mergedSchema; - - const newFormData = getDefaultFormState( - validator, - schema, - formData, - rootSchema, - true, - ); - - const returnedValidation = await validation(newFormData); + const returnedValidation = await validation(formData); const hasErrors = Object.values(returnedValidation).some( i => i.__errors?.length, @@ -138,7 +131,7 @@ export const Stepper = (props: StepperProps) => { return stepNum; }); } - setFormState(current => ({ ...current, ...newFormData })); + setFormState(current => ({ ...current, ...formData })); }; return ( @@ -165,6 +158,7 @@ export const Stepper = (props: StepperProps) => { onSubmit={handleNext} fields={extensions} showErrorList={false} + onChange={handleChange} {...(props.FormProps ?? {})} >
From 4e5bd3124207964981120edc93c4e3fede6cec29 Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Thu, 22 Dec 2022 21:37:56 +0000 Subject: [PATCH 90/94] update changeset Signed-off-by: Paul Cowan --- .changeset/metal-hotels-deliver.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/metal-hotels-deliver.md b/.changeset/metal-hotels-deliver.md index f2ce925bc2..6b7f9c109b 100644 --- a/.changeset/metal-hotels-deliver.md +++ b/.changeset/metal-hotels-deliver.md @@ -2,4 +2,4 @@ '@backstage/plugin-scaffolder': patch --- -'add onChange handler`to`Stepper` component +add `onChange` handler to`Stepper` component From 0b0bd3379bdbfef88944a55c54827d320114e0d2 Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Fri, 23 Dec 2022 10:57:31 +0000 Subject: [PATCH 91/94] merge formData with current in Stepper onChange Signed-off-by: Paul Cowan --- .../scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx index d59fb7f9c1..d4d57eed22 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx @@ -102,7 +102,8 @@ export const Stepper = (props: StepperProps) => { }; const handleChange = useCallback( - (e: IChangeEvent) => setFormState(e.formData), + (e: IChangeEvent) => + setFormState(current => ({ ...current, ...e.formData })), [setFormState], ); From 89faf7c9b5c9d75f40d4dd05c416787c54588bf8 Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Fri, 23 Dec 2022 12:11:07 +0000 Subject: [PATCH 92/94] test nested form data in multiple steps Signed-off-by: Paul Cowan --- .../Stepper/Stepper.test.tsx | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.test.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.test.tsx index af86abf1c6..f40e467a14 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.test.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.test.tsx @@ -20,6 +20,7 @@ import { renderInTestApp } from '@backstage/test-utils'; import { act, fireEvent } from '@testing-library/react'; import type { RJSFValidationError } from '@rjsf/utils'; import { JsonValue } from '@backstage/types'; +import { NextFieldExtensionComponentProps } from '../../../extensions/types'; describe('Stepper', () => { it('should render the step titles for each step of the manifest', async () => { @@ -110,6 +111,96 @@ describe('Stepper', () => { ); }); + it('should merge nested formData correctly in multiple steps', async () => { + const Repo = ({ + onChange, + }: NextFieldExtensionComponentProps<{ repository: string }, any>) => ( + onChange({ repository: e.target.value })} + defaultValue="" + /> + ); + + const Owner = ({ + onChange, + }: NextFieldExtensionComponentProps<{ owner: string }, any>) => ( + onChange({ owner: e.target.value })} + defaultValue="" + /> + ); + + const manifest: TemplateParameterSchema = { + steps: [ + { + title: 'Step 1', + schema: { + properties: { + first: { + type: 'object', + 'ui:field': 'Repo', + }, + }, + }, + }, + { + title: 'Step 2', + schema: { + properties: { + second: { + type: 'object', + 'ui:field': 'Owner', + }, + }, + }, + }, + ], + title: 'React JSON Schema Form Test', + }; + + const onComplete = jest.fn(async (values: Record) => { + expect(values).toEqual({ + first: { repository: 'Repo' }, + second: { owner: 'Owner' }, + }); + }); + + const { getByRole } = await renderInTestApp( + , + ); + + await fireEvent.change(getByRole('textbox', { name: 'repo' }), { + target: { value: 'Repo' }, + }); + + await act(async () => { + await fireEvent.click(getByRole('button', { name: 'Next' })); + }); + + await fireEvent.change(getByRole('textbox', { name: 'owner' }), { + target: { value: 'Owner' }, + }); + + await act(async () => { + await fireEvent.click(getByRole('button', { name: 'Review' })); + }); + + await act(async () => { + await fireEvent.click(getByRole('button', { name: 'Create' })); + }); + }); + it('should render custom field extensions properly', async () => { const MockComponent = () => { return

im a custom field extension

; From c4d2226ef2b7a9ea3f5241db2f71302461be2b28 Mon Sep 17 00:00:00 2001 From: Paul Cowan Date: Fri, 23 Dec 2022 12:15:14 +0000 Subject: [PATCH 93/94] ensure mock is called in Stepper test Signed-off-by: Paul Cowan --- .../src/next/TemplateWizardPage/Stepper/Stepper.test.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.test.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.test.tsx index f40e467a14..2660eed47d 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.test.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.test.tsx @@ -199,6 +199,8 @@ describe('Stepper', () => { await act(async () => { await fireEvent.click(getByRole('button', { name: 'Create' })); }); + + expect(onComplete).toHaveBeenCalled(); }); it('should render custom field extensions properly', async () => { From 17da0d3ceba7d4fa26e2893b8f089cadca307202 Mon Sep 17 00:00:00 2001 From: John Fletcher Date: Fri, 23 Dec 2022 13:48:11 +0100 Subject: [PATCH 94/94] Minor comprehension fix for writing template example Signed-off-by: John Fletcher --- docs/features/software-templates/writing-templates.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/features/software-templates/writing-templates.md b/docs/features/software-templates/writing-templates.md index 5c9340b393..71aa0ab758 100644 --- a/docs/features/software-templates/writing-templates.md +++ b/docs/features/software-templates/writing-templates.md @@ -165,11 +165,11 @@ this: "firstName": { "ui:autofocus": true, "ui:emptyValue": "", - "ui:autocomplete": "family-name" + "ui:autocomplete": "given-name" }, "lastName": { "ui:emptyValue": "", - "ui:autocomplete": "given-name" + "ui:autocomplete": "family-name" }, "nicknames": { "ui:options":{ @@ -211,12 +211,12 @@ spec: default: Chuck ui:autofocus: true ui:emptyValue: '' - ui:autocomplete: family-name + ui:autocomplete: given-name lastName: type: string title: Last name ui:emptyValue: '' - ui:autocomplete: given-name + ui:autocomplete: family-name nicknames: type: array items: