diff --git a/diffs/0.3.44..0.5.7-next.1.diff b/diffs/0.3.44..0.5.7-next.1.diff new file mode 100644 index 00000000..e9657b6c --- /dev/null +++ b/diffs/0.3.44..0.5.7-next.1.diff @@ -0,0 +1,1943 @@ +diff --git a/.dockerignore b/.dockerignore +index 63c9c34..05edb62 100644 +--- a/.dockerignore ++++ b/.dockerignore +@@ -1,5 +1,8 @@ + .git ++.yarn/cache ++.yarn/install-state.gz + node_modules +-packages +-!packages/backend/dist ++packages/*/src ++packages/*/node_modules + plugins ++*.local.yaml +diff --git a/.eslintignore b/.eslintignore +new file mode 100644 +index 0000000..e5b1994 +--- /dev/null ++++ b/.eslintignore +@@ -0,0 +1 @@ ++playwright.config.ts +diff --git a/.gitignore b/.gitignore +index d16a8d3..fbf8139 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -17,2 +17,11 @@ node_modules/ + ++# Yarn 3 files ++.pnp.* ++.yarn/* ++!.yarn/patches ++!.yarn/plugins ++!.yarn/releases ++!.yarn/sdks ++!.yarn/versions ++ + # Node version directives +@@ -39 +48,7 @@ site + *-credentials.yaml ++ ++# vscode database functionality support files ++*.session.sql ++ ++# E2E test reports ++e2e-test-report/ +diff --git a/app-config.production.yaml b/app-config.production.yaml +index 92f4574..8f0751c 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -1,8 +1,35 @@ + app: +- # Should be the same as backend.baseUrl when using the `app-backend` plugin +- baseUrl: http://localhost:7000 ++ # Should be the same as backend.baseUrl when using the `app-backend` plugin. ++ baseUrl: http://localhost:7007 + + backend: +- baseUrl: http://localhost:7000 +- listen: +- port: 7000 ++ # Note that the baseUrl should be the URL that the browser and other clients ++ # should use when communicating with the backend, i.e. it needs to be ++ # reachable not just from within the backend host, but from all of your ++ # callers. When its value is "http://localhost:7007", it's strictly private ++ # and can't be reached by others. ++ baseUrl: http://localhost:7007 ++ # The listener can also be expressed as a single : string. In this case we bind to ++ # all interfaces, the most permissive setting. The right value depends on your specific deployment. ++ listen: ':7007' ++ ++ # config options: https://node-postgres.com/api/client ++ database: ++ client: pg ++ connection: ++ host: ${POSTGRES_HOST} ++ port: ${POSTGRES_PORT} ++ user: ${POSTGRES_USER} ++ password: ${POSTGRES_PASSWORD} ++ # https://node-postgres.com/features/ssl ++ # you can set the sslmode configuration option via the `PGSSLMODE` environment variable ++ # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require) ++ # ssl: ++ # ca: # if you have a CA file and want to verify it you can uncomment this section ++ # $file: /ca/server.crt ++ ++catalog: ++ # Overrides the default list locations from app-config.yaml as these contain example data. ++ # See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details ++ # on how to get entities into the catalog. ++ locations: [] +diff --git a/app-config.yaml b/app-config.yaml +index 93b0c3f..08d2abf 100644 +--- a/app-config.yaml ++++ b/app-config.yaml +@@ -8,5 +8,13 @@ organization: + backend: +- baseUrl: http://localhost:7000 ++ # Used for enabling authentication, secret is shared by all backend plugins ++ # See https://backstage.io/docs/auth/service-to-service-auth for ++ # information on the format ++ # auth: ++ # keys: ++ # - secret: ${BACKEND_SECRET} ++ baseUrl: http://localhost:7007 + listen: +- port: 7000 ++ port: 7007 ++ # Uncomment the following host directive to bind to specific interfaces ++ # host: 127.0.0.1 + csp: +@@ -17,9 +25,9 @@ backend: + origin: http://localhost:3000 +- methods: [GET, POST, PUT, DELETE] ++ methods: [GET, HEAD, PATCH, POST, PUT, DELETE] + credentials: true ++ # This is for local development only, it is not recommended to use this in production ++ # The production database configuration is stored in app-config.production.yaml + database: +- client: sqlite3 ++ client: better-sqlite3 + connection: ':memory:' +- cache: +- store: memory + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir +@@ -29,2 +37,4 @@ integrations: + - host: github.com ++ # This is a Personal Access Token or PAT from GitHub. You can find out how to generate this token, and more information ++ # about setting up the GitHub integration here: https://backstage.io/docs/getting-started/configuration#setting-up-a-github-integration + token: ${GITHUB_TOKEN} +@@ -36,5 +46,8 @@ integrations: + proxy: +- '/test': +- target: 'https://example.com' +- changeOrigin: true ++ ### Example for how to add a proxy endpoint for the frontend. ++ ### A typical reason to do this is to handle HTTPS and CORS for internal services. ++ # endpoints: ++ # '/test': ++ # target: 'https://example.com' ++ # changeOrigin: true + +@@ -56,46 +69,35 @@ auth: + scaffolder: +- github: +- token: ${GITHUB_TOKEN} +- visibility: public # or 'internal' or 'private' ++ # see https://backstage.io/docs/features/software-templates/configuration for software template options + + catalog: ++ import: ++ entityFilename: catalog-info.yaml ++ pullRequestBranchName: backstage-integration + rules: +- - allow: [Component, System, API, Group, User, Resource, Location] ++ - allow: [Component, System, API, Resource, Location] + locations: +- # Backstage example components +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-components.yaml +- +- # Backstage example systems +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-systems.yaml +- +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml +- +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml ++ # Local example data, file locations are relative to the backend process, typically `packages/backend` ++ - type: file ++ target: ../../examples/entities.yaml + +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.yaml +- +- # Backstage example templates +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/react-ssr-template/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/springboot-grpc-template/template.yaml ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml + rules: + - allow: [Template] +- - type: url +- target: https://github.com/spotify/cookiecutter-golang/blob/master/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/docs-template/template.yaml ++ ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml + rules: +- - allow: [Template] ++ - allow: [User, Group] ++ ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml ++ ++ ## Uncomment these lines to add an example org ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme-corp.yaml ++ # rules: ++ # - allow: [User, Group] +diff --git a/backstage.json b/backstage.json +new file mode 100644 +index 0000000..707af29 +--- /dev/null ++++ b/backstage.json +@@ -0,0 +1,3 @@ ++{ ++ "version": "1.20.0-next.1" ++} +diff --git a/examples/entities.yaml b/examples/entities.yaml +new file mode 100644 +index 0000000..447e8b1 +--- /dev/null ++++ b/examples/entities.yaml +@@ -0,0 +1,41 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system ++apiVersion: backstage.io/v1alpha1 ++kind: System ++metadata: ++ name: examples ++spec: ++ owner: guests ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: example-website ++spec: ++ type: website ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ providesApis: [example-grpc-api] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api ++apiVersion: backstage.io/v1alpha1 ++kind: API ++metadata: ++ name: example-grpc-api ++spec: ++ type: grpc ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ definition: | ++ syntax = "proto3"; ++ ++ service Exampler { ++ rpc Example (ExampleMessage) returns (ExampleMessage) {}; ++ } ++ ++ message ExampleMessage { ++ string example = 1; ++ }; +diff --git a/examples/org.yaml b/examples/org.yaml +new file mode 100644 +index 0000000..a10e81f +--- /dev/null ++++ b/examples/org.yaml +@@ -0,0 +1,17 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user ++apiVersion: backstage.io/v1alpha1 ++kind: User ++metadata: ++ name: guest ++spec: ++ memberOf: [guests] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group ++apiVersion: backstage.io/v1alpha1 ++kind: Group ++metadata: ++ name: guests ++spec: ++ type: team ++ children: [] +diff --git a/examples/template/content/catalog-info.yaml b/examples/template/content/catalog-info.yaml +new file mode 100644 +index 0000000..d4ccca4 +--- /dev/null ++++ b/examples/template/content/catalog-info.yaml +@@ -0,0 +1,8 @@ ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: ${{ values.name | dump }} ++spec: ++ type: service ++ owner: user:guest ++ lifecycle: experimental +diff --git a/examples/template/content/index.js b/examples/template/content/index.js +new file mode 100644 +index 0000000..071ce5a +--- /dev/null ++++ b/examples/template/content/index.js +@@ -0,0 +1 @@ ++console.log('Hello from ${{ values.name }}!'); +diff --git a/examples/template/content/package.json b/examples/template/content/package.json +new file mode 100644 +index 0000000..86f968a +--- /dev/null ++++ b/examples/template/content/package.json +@@ -0,0 +1,5 @@ ++{ ++ "name": "${{ values.name }}", ++ "private": true, ++ "dependencies": {} ++} +diff --git a/examples/template/template.yaml b/examples/template/template.yaml +new file mode 100644 +index 0000000..33f262b +--- /dev/null ++++ b/examples/template/template.yaml +@@ -0,0 +1,74 @@ ++apiVersion: scaffolder.backstage.io/v1beta3 ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template ++kind: Template ++metadata: ++ name: example-nodejs-template ++ title: Example Node.js Template ++ description: An example template for the scaffolder that creates a simple Node.js service ++spec: ++ owner: user:guest ++ type: service ++ ++ # These parameters are used to generate the input form in the frontend, and are ++ # used to gather input data for the execution of the template. ++ parameters: ++ - title: Fill in some steps ++ required: ++ - name ++ properties: ++ name: ++ title: Name ++ type: string ++ description: Unique name of the component ++ ui:autofocus: true ++ ui:options: ++ rows: 5 ++ - title: Choose a location ++ required: ++ - repoUrl ++ properties: ++ repoUrl: ++ title: Repository Location ++ type: string ++ ui:field: RepoUrlPicker ++ ui:options: ++ allowedHosts: ++ - github.com ++ ++ # These steps are executed in the scaffolder backend, using data that we gathered ++ # via the parameters above. ++ steps: ++ # Each step executes an action, in this case one templates files into the working directory. ++ - id: fetch-base ++ name: Fetch Base ++ action: fetch:template ++ input: ++ url: ./content ++ values: ++ name: ${{ parameters.name }} ++ ++ # This step publishes the contents of the working directory to GitHub. ++ - id: publish ++ name: Publish ++ action: publish:github ++ input: ++ allowedHosts: ['github.com'] ++ description: This is ${{ parameters.name }} ++ repoUrl: ${{ parameters.repoUrl }} ++ ++ # The final step is to register our new component in the catalog. ++ - id: register ++ name: Register ++ action: catalog:register ++ input: ++ repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} ++ catalogInfoPath: '/catalog-info.yaml' ++ ++ # Outputs are displayed to the user after a successful execution of the template. ++ output: ++ links: ++ - title: Repository ++ url: ${{ steps['publish'].output.remoteUrl }} ++ - title: Open in catalog ++ icon: catalog ++ entityRef: ${{ steps['register'].output.entityRef }} +diff --git a/lerna.json b/lerna.json +index 322929d..529a62f 100644 +--- a/lerna.json ++++ b/lerna.json +@@ -3,4 +3,4 @@ + "npmClient": "yarn", +- "useWorkspaces": true, +- "version": "0.1.0" ++ "version": "0.1.0", ++ "$schema": "node_modules/lerna/schemas/lerna-schema.json" + } +diff --git a/package.json b/package.json +index fca07bd..2364da8 100644 +--- a/package.json ++++ b/package.json +@@ -5,3 +5,3 @@ + "engines": { +- "node": "14 || 16" ++ "node": "18 || 20" + }, +@@ -11,3 +11,4 @@ + "start-backend": "yarn workspace backend start", +- "build": "lerna run build", ++ "build:backend": "yarn workspace backend build", ++ "build:all": "backstage-cli repo build --all", + "build-image": "yarn workspace backend build-image", +@@ -15,15 +16,11 @@ + "tsc:full": "tsc --skipLibCheck false --incremental false", +- "clean": "backstage-cli clean && lerna run clean", +- "diff": "lerna run diff --", +- "test": "backstage-cli test", +- "test:all": "lerna run test -- --coverage", +- "lint": "lerna run lint --since origin/master --", +- "lint:all": "lerna run lint --", ++ "clean": "backstage-cli repo clean", ++ "test": "backstage-cli repo test", ++ "test:all": "backstage-cli repo test --coverage", ++ "test:e2e": "playwright test", ++ "fix": "backstage-cli repo fix", ++ "lint": "backstage-cli repo lint --since origin/master", ++ "lint:all": "backstage-cli repo lint", + "prettier:check": "prettier --check .", +- "create-plugin": "backstage-cli create-plugin --scope internal --no-private", +- "remove-plugin": "backstage-cli remove-plugin" +- }, +- "resolutions": { +- "graphql-language-service-interface": "2.8.2", +- "graphql-language-service-parser": "1.9.0" ++ "new": "backstage-cli new --scope internal" + }, +@@ -36,7 +33,15 @@ + "devDependencies": { +- "@backstage/cli": "^0.7.15", +- "@spotify/prettier-config": "^11.0.0", +- "concurrently": "^6.0.0", +- "lerna": "^4.0.0", +- "prettier": "^2.3.2" ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/e2e-test-utils": "^0.1.0", ++ "@playwright/test": "^1.32.3", ++ "@spotify/prettier-config": "^12.0.0", ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^9.0.0", ++ "prettier": "^2.3.2", ++ "typescript": "~5.2.0" ++ }, ++ "resolutions": { ++ "@types/react": "^17", ++ "@types/react-dom": "^17" + }, +@@ -44,3 +49,3 @@ + "lint-staged": { +- "*.{js,jsx,ts,tsx}": [ ++ "*.{js,jsx,ts,tsx,mjs,cjs}": [ + "eslint --fix", +@@ -51,7 +56,2 @@ + ] +- }, +- "jest": { +- "transformModules": [ +- "@asyncapi/react-component" +- ] + } +diff --git a/packages/README.md b/packages/README.md +new file mode 100644 +index 0000000..6327fa0 +--- /dev/null ++++ b/packages/README.md +@@ -0,0 +1,9 @@ ++# The Packages Folder ++ ++This is where your own applications and centrally managed libraries live, each ++in a separate folder of its own. ++ ++From the start there's an `app` folder (for the frontend) and a `backend` folder ++(for the Node backend), but you can also add more modules in here that house ++your core additions and adaptations, such as themes, common React component ++libraries, utilities, and similar. +diff --git a/packages/app/.eslintignore b/packages/app/.eslintignore +new file mode 100644 +index 0000000..a48cf0d +--- /dev/null ++++ b/packages/app/.eslintignore +@@ -0,0 +1 @@ ++public +diff --git a/packages/app/.eslintrc.js b/packages/app/.eslintrc.js +index 13573ef..e2a53a6 100644 +--- a/packages/app/.eslintrc.js ++++ b/packages/app/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/app/cypress.json b/packages/app/cypress.json +deleted file mode 100644 +index 5de7ebf..0000000 +--- a/packages/app/cypress.json ++++ /dev/null +@@ -1,5 +0,0 @@ +-{ +- "baseUrl": "http://localhost:3001", +- "fixturesFolder": false, +- "pluginsFile": false +-} +diff --git a/packages/app/cypress/.eslintrc.json b/packages/app/cypress/.eslintrc.json +deleted file mode 100644 +index 2b3a458..0000000 +--- a/packages/app/cypress/.eslintrc.json ++++ /dev/null +@@ -1,21 +0,0 @@ +-{ +- "plugins": ["cypress"], +- "extends": ["plugin:cypress/recommended"], +- "rules": { +- "jest/expect-expect": [ +- "error", +- { +- "assertFunctionNames": ["expect", "cy.contains"] +- } +- ], +- "import/no-extraneous-dependencies": [ +- "error", +- { +- "devDependencies": true, +- "optionalDependencies": true, +- "peerDependencies": true, +- "bundledDependencies": true +- } +- ] +- } +-} +diff --git a/packages/app/cypress/integration/app.js b/packages/app/cypress/integration/app.js +deleted file mode 100644 +index 43fb2e3..0000000 +--- a/packages/app/cypress/integration/app.js ++++ /dev/null +@@ -1,6 +0,0 @@ +-describe('App', () => { +- it('should render the catalog', () => { +- cy.visit('/'); +- cy.contains('My Company Catalog'); +- }); +-}); +diff --git a/packages/app/e2e-tests/app.test.ts b/packages/app/e2e-tests/app.test.ts +new file mode 100644 +index 0000000..d45bc0d +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,23 @@ ++/* ++ * Copyright 2020 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { test, expect } from '@playwright/test'; ++ ++test('App should render the welcome page', async ({ page }) => { ++ await page.goto('/'); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 6d348b5..2100405 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,22 +5,38 @@ + "bundled": true, ++ "backstage": { ++ "role": "frontend" ++ }, ++ "scripts": { ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "clean": "backstage-cli package clean", ++ "test": "backstage-cli package test", ++ "lint": "backstage-cli package lint" ++ }, + "dependencies": { +- "@backstage/catalog-model": "^0.9.4", +- "@backstage/cli": "^0.7.15", +- "@backstage/core-app-api": "^0.1.16", +- "@backstage/core-components": "^0.6.1", +- "@backstage/core-plugin-api": "^0.1.10", +- "@backstage/integration-react": "^0.1.11", +- "@backstage/plugin-api-docs": "^0.6.11", +- "@backstage/plugin-catalog": "^0.7.0", +- "@backstage/plugin-catalog-import": "^0.7.1", +- "@backstage/plugin-catalog-react": "^0.5.2", +- "@backstage/plugin-github-actions": "^0.4.21", +- "@backstage/plugin-org": "^0.3.26", +- "@backstage/plugin-scaffolder": "^0.11.7", +- "@backstage/plugin-search": "^0.4.14", +- "@backstage/plugin-tech-radar": "^0.4.10", +- "@backstage/plugin-techdocs": "^0.12.1", +- "@backstage/plugin-user-settings": "^0.3.8", +- "@backstage/test-utils": "^0.1.18", +- "@backstage/theme": "^0.2.10", ++ "@backstage/app-defaults": "^1.4.5-next.1", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/core-app-api": "^1.11.1-next.0", ++ "@backstage/core-components": "^0.13.8-next.1", ++ "@backstage/core-plugin-api": "^1.8.0-next.0", ++ "@backstage/integration-react": "^1.1.21-next.1", ++ "@backstage/plugin-api-docs": "^0.9.13-next.1", ++ "@backstage/plugin-catalog": "^1.15.0-next.1", ++ "@backstage/plugin-catalog-common": "^1.0.17", ++ "@backstage/plugin-catalog-graph": "^0.2.38-next.1", ++ "@backstage/plugin-catalog-import": "^0.10.2-next.1", ++ "@backstage/plugin-catalog-react": "^1.9.0-next.1", ++ "@backstage/plugin-github-actions": "^0.6.7-next.1", ++ "@backstage/plugin-org": "^0.6.16-next.1", ++ "@backstage/plugin-permission-react": "^0.4.17-next.0", ++ "@backstage/plugin-scaffolder": "^1.16.0-next.1", ++ "@backstage/plugin-search": "^1.4.2-next.1", ++ "@backstage/plugin-search-react": "^1.7.2-next.1", ++ "@backstage/plugin-tech-radar": "^0.6.10-next.1", ++ "@backstage/plugin-techdocs": "^1.8.1-next.1", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.2-next.1", ++ "@backstage/plugin-techdocs-react": "^1.1.13-next.1", ++ "@backstage/plugin-user-settings": "^0.7.12-next.1", ++ "@backstage/theme": "^0.4.4-next.0", + "@material-ui/core": "^4.12.2", +@@ -28,30 +44,17 @@ + "history": "^5.0.0", +- "react": "^16.13.1", +- "react-dom": "^16.13.1", +- "react-router": "6.0.0-beta.0", +- "react-router-dom": "6.0.0-beta.0", +- "react-use": "^15.3.3" ++ "react": "^17.0.2", ++ "react-dom": "^17.0.2", ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { ++ "@backstage/test-utils": "^1.4.5-next.0", ++ "@playwright/test": "^1.32.3", + "@testing-library/jest-dom": "^5.10.1", +- "@testing-library/react": "^10.4.1", +- "@testing-library/user-event": "^12.0.7", +- "@types/jest": "^26.0.7", +- "@types/node": "^14.14.32", ++ "@testing-library/react": "^12.1.3", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^8.0.0", + "@types/react-dom": "*", +- "cross-env": "^7.0.0", +- "cypress": "^7.3.0", +- "eslint-plugin-cypress": "^2.10.3", +- "start-server-and-test": "^1.10.11" +- }, +- "scripts": { +- "start": "backstage-cli app:serve", +- "build": "backstage-cli app:build", +- "test": "backstage-cli test", +- "lint": "backstage-cli lint", +- "clean": "backstage-cli clean", +- "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", +- "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", +- "cy:dev": "cypress open", +- "cy:run": "cypress run" ++ "cross-env": "^7.0.0" + }, +diff --git a/packages/app/public/index.html b/packages/app/public/index.html +index b93896c..fc758ee 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -10,3 +10,2 @@ + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index 82bc479..b94cac7 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -12,5 +12,5 @@ describe('App', () => { + app: { title: 'Test' }, +- backend: { baseUrl: 'http://localhost:7000' }, ++ backend: { baseUrl: 'http://localhost:7007' }, + techdocs: { +- storageUrl: 'http://localhost:7000/api/techdocs/static/docs', ++ storageUrl: 'http://localhost:7007/api/techdocs/static/docs', + }, +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 4cd8368..8d62f29 100644 +--- a/packages/app/src/App.tsx ++++ b/packages/app/src/App.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { Navigate, Route } from 'react-router'; ++import { Navigate, Route } from 'react-router-dom'; + import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; +@@ -13,2 +13,3 @@ import { + import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; ++import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +@@ -16,3 +17,2 @@ import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -21,2 +21,4 @@ import { + } from '@backstage/plugin-techdocs'; ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; + import { UserSettingsPage } from '@backstage/plugin-user-settings'; +@@ -28,3 +30,7 @@ import { Root } from './components/Root'; + import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; +-import { createApp, FlatRoutes } from '@backstage/core-app-api'; ++import { createApp } from '@backstage/app-defaults'; ++import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; ++import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; ++import { RequirePermission } from '@backstage/plugin-permission-react'; ++import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; + +@@ -36,5 +42,6 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); + bind(apiDocsPlugin.externalRoutes, { +- createComponent: scaffolderPlugin.routes.root, ++ registerApi: catalogImportPlugin.routes.importPage, + }); +@@ -42,2 +49,6 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, ++ }); ++ bind(orgPlugin.externalRoutes, { ++ catalogIndex: catalogPlugin.routes.catalogIndex, + }); +@@ -46,8 +57,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -59,5 +67,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -72,3 +82,10 @@ const routes = ( + /> +- } /> ++ ++ ++ ++ } ++ /> + }> +@@ -77,2 +94,3 @@ const routes = ( + } /> ++ } /> + +@@ -80,4 +98,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -87,5 +105,3 @@ const App = () => ( + +- ++ , + ); +- +-export default App; +diff --git a/packages/app/src/components/Root/LogoFull.tsx b/packages/app/src/components/Root/LogoFull.tsx +index c7b1c84..47e3b73 100644 +--- a/packages/app/src/components/Root/LogoFull.tsx ++++ b/packages/app/src/components/Root/LogoFull.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/LogoIcon.tsx b/packages/app/src/components/Root/LogoIcon.tsx +index 073cf6e..7eae8c7 100644 +--- a/packages/app/src/components/Root/LogoIcon.tsx ++++ b/packages/app/src/components/Root/LogoIcon.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx +index 198e7ec..6768b48 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,19 +1,3 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- +-import React, { useContext, PropsWithChildren } from 'react'; +-import { Link, makeStyles } from '@material-ui/core'; ++import React, { PropsWithChildren } from 'react'; ++import { makeStyles } from '@material-ui/core'; + import HomeIcon from '@material-ui/icons/Home'; +@@ -25,15 +9,21 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; +-import { Settings as SidebarSettings } from '@backstage/plugin-user-settings'; +-import { SidebarSearch } from '@backstage/plugin-search'; ++import { ++ Settings as SidebarSettings, ++ UserSettingsSignInAvatar, ++} from '@backstage/plugin-user-settings'; ++import { SidebarSearchModal } from '@backstage/plugin-search'; + import { + Sidebar, +- SidebarPage, + sidebarConfig, +- SidebarContext, +- SidebarItem, + SidebarDivider, +- SidebarSpace, ++ SidebarGroup, ++ SidebarItem, ++ SidebarPage, + SidebarScrollWrapper, ++ SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; ++import MenuIcon from '@material-ui/icons/Menu'; ++import SearchIcon from '@material-ui/icons/Search'; + +@@ -56,3 +46,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -60,8 +50,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -76,17 +61,27 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ } to="/search"> ++ ++ + +- {/* Global nav, not org-specific */} +- +- +- +- +- {/* End global nav */} +- +- +- +- ++ }> ++ {/* Global nav, not org-specific */} ++ ++ ++ ++ ++ {/* End global nav */} ++ ++ ++ ++ ++ + + +- ++ } ++ to="/settings" ++ > ++ ++ + +diff --git a/packages/app/src/components/Root/index.ts b/packages/app/src/components/Root/index.ts +index dff706f..3528881 100644 +--- a/packages/app/src/components/Root/index.ts ++++ b/packages/app/src/components/Root/index.ts +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + export { Root } from './Root'; +diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx +index d3b4b78..6722ea2 100644 +--- a/packages/app/src/components/catalog/EntityPage.tsx ++++ b/packages/app/src/components/catalog/EntityPage.tsx +@@ -1,16 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ + import React from 'react'; +@@ -29,3 +14,2 @@ import { + EntityDependsOnResourcesCard, +- EntitySystemDiagramCard, + EntityHasComponentsCard, +@@ -43,2 +27,4 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +@@ -56,2 +42,27 @@ import { EntityTechdocsContent } from '@backstage/plugin-techdocs'; + import { EmptyState } from '@backstage/core-components'; ++import { ++ Direction, ++ EntityCatalogGraphCard, ++} from '@backstage/plugin-catalog-graph'; ++import { ++ RELATION_API_CONSUMED_BY, ++ RELATION_API_PROVIDED_BY, ++ RELATION_CONSUMES_API, ++ RELATION_DEPENDENCY_OF, ++ RELATION_DEPENDS_ON, ++ RELATION_HAS_PART, ++ RELATION_PART_OF, ++ RELATION_PROVIDES_API, ++} from '@backstage/catalog-model'; ++ ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); + +@@ -94,2 +105,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -110,2 +129,6 @@ const overviewContent = ( + ++ ++ ++ ++ + +@@ -152,3 +175,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -179,3 +202,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -198,3 +221,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -225,2 +248,5 @@ const apiPage = ( + ++ ++ ++ + +@@ -292,3 +318,9 @@ const systemPage = ( + +- ++ ++ ++ ++ ++ ++ ++ + +@@ -304,3 +336,19 @@ const systemPage = ( + +- ++ + +@@ -317,2 +365,5 @@ const domainPage = ( + ++ ++ ++ + +diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx +index 7b3c2b2..1788dde 100644 +--- a/packages/app/src/components/search/SearchPage.tsx ++++ b/packages/app/src/components/search/SearchPage.tsx +@@ -1,5 +1,12 @@ + import React from 'react'; +-import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; ++import { makeStyles, Theme, Grid, Paper } from '@material-ui/core'; + +-import { CatalogResultListItem } from '@backstage/plugin-catalog'; ++import { CatalogSearchResultListItem } from '@backstage/plugin-catalog'; ++import { ++ catalogApiRef, ++ CATALOG_FILTER_EXISTS, ++} from '@backstage/plugin-catalog-react'; ++import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; ++ ++import { SearchType } from '@backstage/plugin-search'; + import { +@@ -8,5 +15,13 @@ import { + SearchResult, +- DefaultResultListItem, +-} from '@backstage/plugin-search'; +-import { Content, Header, Page } from '@backstage/core-components'; ++ SearchPagination, ++ useSearch, ++} from '@backstage/plugin-search-react'; ++import { ++ CatalogIcon, ++ Content, ++ DocsIcon, ++ Header, ++ Page, ++} from '@backstage/core-components'; ++import { useApi } from '@backstage/core-plugin-api'; + +@@ -18,2 +33,3 @@ const useStyles = makeStyles((theme: Theme) => ({ + padding: theme.spacing(2), ++ marginTop: theme.spacing(2), + }, +@@ -28,2 +44,4 @@ const SearchPage = () => { + const classes = useStyles(); ++ const { types } = useSearch(); ++ const catalogApi = useApi(catalogApiRef); + +@@ -36,3 +54,3 @@ const SearchPage = () => { + +- ++ + +@@ -40,5 +58,43 @@ const SearchPage = () => { + ++ , ++ }, ++ { ++ value: 'techdocs', ++ name: 'Documentation', ++ icon: , ++ }, ++ ]} ++ /> + ++ {types.includes('techdocs') && ( ++ { ++ // Return a list of entities which are documented. ++ const { items } = await catalogApi.getEntities({ ++ fields: ['metadata.name'], ++ filter: { ++ 'metadata.annotations.backstage.io/techdocs-ref': ++ CATALOG_FILTER_EXISTS, ++ }, ++ }); ++ ++ const names = items.map(entity => entity.metadata.name); ++ names.sort(); ++ return names; ++ }} ++ /> ++ )} + { + className={classes.filter} ++ label="Lifecycle" + name="lifecycle" +@@ -54,25 +111,6 @@ const SearchPage = () => { + ++ + +- {({ results }) => ( +- +- {results.map(({ type, document }) => { +- switch (type) { +- case 'software-catalog': +- return ( +- +- ); +- default: +- return ( +- +- ); +- } +- })} +- +- )} ++ } /> ++ } /> + +diff --git a/packages/backend/.eslintrc.js b/packages/backend/.eslintrc.js +index 16a033d..e2a53a6 100644 +--- a/packages/backend/.eslintrc.js ++++ b/packages/backend/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint.backend')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index 31231a3..18548e9 100644 +--- a/packages/backend/Dockerfile ++++ b/packages/backend/Dockerfile +@@ -7,3 +7,3 @@ + # yarn tsc +-# yarn build ++# yarn build:backend + # +@@ -11,6 +11,29 @@ + +-FROM node:14-buster-slim ++FROM node:18-bookworm-slim + ++# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends python3 g++ build-essential && \ ++ yarn config set python /usr/bin/python3 ++ ++# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, ++# in which case you should also move better-sqlite3 to "devDependencies" in package.json. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends libsqlite3-dev ++ ++# From here on we use the least-privileged `node` user to run the backend. ++USER node ++ ++# This should create the app dir as `node`. ++# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. ++# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. + WORKDIR /app + ++# This switches many Node.js dependencies to production mode. ++ENV NODE_ENV production ++ + # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. +@@ -18,11 +41,12 @@ WORKDIR /app + # and along with yarn.lock and the root package.json, that's enough to run yarn install. +-COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ ++COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ + RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz + +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + + # Then copy the rest of the backend bundle, along with any other files we might want. +-COPY packages/backend/dist/bundle.tar.gz app-config.yaml ./ ++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"] +diff --git a/packages/backend/README.md b/packages/backend/README.md +index 81e0f80..3607b0a 100644 +--- a/packages/backend/README.md ++++ b/packages/backend/README.md +@@ -18,4 +18,2 @@ To run the example backend, first go to the project root and run + yarn install +-yarn tsc +-yarn build + ``` +@@ -27,7 +25,2 @@ After that, go to the `packages/backend` directory and run + ```bash +-AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x \ +-AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x \ +-AUTH_OAUTH2_CLIENT_ID=x AUTH_OAUTH2_CLIENT_SECRET=x \ +-AUTH_OAUTH2_AUTH_URL=x AUTH_OAUTH2_TOKEN_URL=x \ +-LOG_LEVEL=debug \ + yarn start +@@ -35,6 +28,6 @@ yarn start + +-Substitute `x` for actual values, or leave them as dummy values just to try out +-the backend without using the auth or sentry features. ++If you want to override any configuration locally, for example adding any secrets, ++you can do so in `app-config.local.yaml`. + +-The backend starts up on port 7000 per default. ++The backend starts up on port 7007 per default. + +@@ -45,3 +38,3 @@ locations to the backend. These are places where the backend can find some + entity descriptor data to consume and serve. For more information, see +-[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog). ++[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). + +@@ -65,2 +58,2 @@ and + - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +-- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) ++- [Backstage Documentation](https://backstage.io/docs) +diff --git a/packages/backend/package.json b/packages/backend/package.json +index 5b58619..f224eaf 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,32 +6,37 @@ + "private": true, ++ "backstage": { ++ "role": "backend" ++ }, + "scripts": { +- "build": "backstage-cli backend:bundle", +- "build-image": "docker build ../.. -f Dockerfile --tag backstage", +- "start": "backstage-cli backend:dev", +- "lint": "backstage-cli lint", +- "test": "backstage-cli test", +- "clean": "backstage-cli clean", +- "migrate:create": "knex migrate:make -x ts" ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "lint": "backstage-cli package lint", ++ "test": "backstage-cli package test", ++ "clean": "backstage-cli package clean", ++ "build-image": "docker build ../.. -f Dockerfile --tag backstage" + }, + "dependencies": { +- "app": "0.0.0", +- "@backstage/backend-common": "^0.9.6", +- "@backstage/catalog-model": "^0.9.4", +- "@backstage/catalog-client": "^0.5.0", +- "@backstage/config": "^0.1.10", +- "@backstage/plugin-app-backend": "^0.3.16", +- "@backstage/plugin-auth-backend": "^0.4.3", +- "@backstage/plugin-catalog-backend": "^0.16.0", +- "@backstage/plugin-proxy-backend": "^0.2.13", +- "@backstage/plugin-scaffolder-backend": "^0.15.7", +- "@backstage/plugin-search-backend": "^0.2.6", +- "@backstage/plugin-search-backend-node": "^0.4.2", +- "@backstage/plugin-techdocs-backend": "^0.10.4", +- "@gitbeaker/node": "^30.2.0", +- "@octokit/rest": "^18.5.3", +- "dockerode": "^3.2.1", ++ "@backstage/backend-common": "^0.19.9-next.1", ++ "@backstage/backend-tasks": "^0.5.12-next.1", ++ "@backstage/catalog-client": "^1.4.5", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/config": "^1.1.1", ++ "@backstage/plugin-app-backend": "^0.3.55-next.1", ++ "@backstage/plugin-auth-backend": "^0.20.0-next.1", ++ "@backstage/plugin-auth-node": "^0.4.1-next.1", ++ "@backstage/plugin-catalog-backend": "^1.15.0-next.1", ++ "@backstage/plugin-permission-common": "^0.7.9", ++ "@backstage/plugin-permission-node": "^0.7.18-next.1", ++ "@backstage/plugin-proxy-backend": "^0.4.5-next.1", ++ "@backstage/plugin-scaffolder-backend": "^1.19.0-next.1", ++ "@backstage/plugin-search-backend": "^1.4.7-next.1", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.16-next.1", ++ "@backstage/plugin-search-backend-node": "^1.2.11-next.1", ++ "@backstage/plugin-techdocs-backend": "^1.9.0-next.1", ++ "app": "link:../app", ++ "better-sqlite3": "^8.0.0", ++ "dockerode": "^3.3.1", + "express": "^4.17.1", + "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "pg": "^8.3.0", + "winston": "^3.2.1" +@@ -39,6 +44,7 @@ + "devDependencies": { +- "@backstage/cli": "^0.7.15", +- "@types/dockerode": "^3.2.1", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@types/dockerode": "^3.3.0", + "@types/express": "^4.17.6", +- "@types/express-serve-static-core": "^4.17.5" ++ "@types/express-serve-static-core": "^4.17.5", ++ "@types/luxon": "^2.0.4" + }, +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index f2b14b2..04c4ff9 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -17,5 +17,7 @@ import { + DatabaseManager, +- SingleHostDiscovery, ++ HostDiscovery, + UrlReaders, ++ ServerTokenManager, + } from '@backstage/backend-common'; ++import { TaskScheduler } from '@backstage/backend-tasks'; + import { Config } from '@backstage/config'; +@@ -29,2 +31,4 @@ import search from './plugins/search'; + import { PluginEnvironment } from './types'; ++import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; + +@@ -33,8 +37,17 @@ function makeCreateEnv(config: Config) { + const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++ const discovery = HostDiscovery.fromConfig(config); ++ const cacheManager = CacheManager.fromConfig(config); ++ const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); ++ const tokenManager = ServerTokenManager.noop(); ++ const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); + +- root.info(`Created UrlReader ${reader}`); ++ const identity = DefaultIdentityClient.create({ ++ discovery, ++ }); ++ const permissions = ServerPermissionClient.fromConfig(config, { ++ discovery, ++ tokenManager, ++ }); + +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++ root.info(`Created UrlReader ${reader}`); + +@@ -44,3 +57,15 @@ function makeCreateEnv(config: Config) { + const cache = cacheManager.forPlugin(plugin); +- return { logger, database, cache, config, reader, discovery }; ++ const scheduler = taskScheduler.forPlugin(plugin); ++ return { ++ logger, ++ database, ++ cache, ++ config, ++ reader, ++ discovery, ++ tokenManager, ++ scheduler, ++ permissions, ++ identity, ++ }; + }; +@@ -70,2 +95,4 @@ async function main() { + apiRouter.use('/search', await search(searchEnv)); ++ ++ // Add backends ABOVE this line; this 404 handler is the catch-all fallback + apiRouter.use(notFoundHandler()); +@@ -85,3 +112,3 @@ module.hot?.accept(); + main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); ++ console.error('Backend failed to start up', error); + process.exit(1); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +index 07fb04f..7c37f68 100644 +--- a/packages/backend/src/plugins/app.ts ++++ b/packages/backend/src/plugins/app.ts +@@ -4,9 +4,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + return await createRouter({ +- logger, +- config, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, + appPackageName: 'app', +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +index 5216510..77eb6aa 100644 +--- a/packages/backend/src/plugins/auth.ts ++++ b/packages/backend/src/plugins/auth.ts +@@ -1,2 +1,6 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; ++import { ++ createRouter, ++ providers, ++ defaultAuthProviderFactories, ++} from '@backstage/plugin-auth-backend'; + import { Router } from 'express'; +@@ -4,9 +8,47 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, database, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, ++ providerFactories: { ++ ...defaultAuthProviderFactories, ++ ++ // This replaces the default GitHub auth provider with a customized one. ++ // The `signIn` option enables sign-in for this provider, using the ++ // identity resolution logic that's provided in the `resolver` callback. ++ // ++ // This particular resolver makes all users share a single "guest" identity. ++ // It should only be used for testing and trying out Backstage. ++ // ++ // If you want to use a production ready resolver you can switch to ++ // the one that is commented out below, it looks up a user entity in the ++ // catalog using the GitHub username of the authenticated user. ++ // That resolver requires you to have user entities populated in the catalog, ++ // for example using https://backstage.io/docs/integrations/github/org ++ // ++ // There are other resolvers to choose from, and you can also create ++ // your own, see the auth documentation for more details: ++ // ++ // https://backstage.io/docs/auth/identity-resolver ++ github: providers.github.create({ ++ signIn: { ++ resolver(_, ctx) { ++ const userRef = 'user:default/guest'; // Must be a full entity reference ++ return ctx.issueToken({ ++ claims: { ++ sub: userRef, // The user's own identity ++ ent: [userRef], // A list of identities that the user claims ownership through ++ }, ++ }); ++ }, ++ // resolver: providers.github.resolvers.usernameMatchingUserEntityName(), ++ }, ++ }), ++ }, ++ }); + } +diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts +index d1ded51..876cb6b 100644 +--- a/packages/backend/src/plugins/catalog.ts ++++ b/packages/backend/src/plugins/catalog.ts +@@ -1,2 +1,3 @@ + import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; ++import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; + import { Router } from 'express'; +@@ -8,2 +9,3 @@ export default async function createPlugin( + const builder = await CatalogBuilder.create(env); ++ builder.addProcessor(new ScaffolderEntitiesProcessor()); + const { processingEngine, router } = await builder.build(); +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +index 506f6d9..54ec393 100644 +--- a/packages/backend/src/plugins/proxy.ts ++++ b/packages/backend/src/plugins/proxy.ts +@@ -4,8 +4,10 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ }); + } +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +index 6be2e97..a12fee2 100644 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ b/packages/backend/src/plugins/scaffolder.ts +@@ -1,5 +1,3 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; + import { CatalogClient } from '@backstage/catalog-client'; + import { createRouter } from '@backstage/plugin-scaffolder-backend'; +-import Docker from 'dockerode'; + import { Router } from 'express'; +@@ -7,20 +5,17 @@ import type { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- database, +- reader, +- discovery, +-}: PluginEnvironment): Promise { +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- const catalogClient = new CatalogClient({ discoveryApi: discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ const catalogClient = new CatalogClient({ ++ discoveryApi: env.discovery, ++ }); + + return await createRouter({ +- containerRunner, +- logger, +- config, +- database, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ reader: env.reader, + catalogClient, +- reader, ++ identity: env.identity, ++ permissions: env.permissions, + }); +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +index 7fc317d..e9469dc 100644 +--- a/packages/backend/src/plugins/search.ts ++++ b/packages/backend/src/plugins/search.ts +@@ -7,18 +7,44 @@ import { + import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; ++import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend'; ++import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; ++import { Router } from 'express'; + +-export default async function createPlugin({ +- logger, +- discovery, +- config, +-}: PluginEnvironment) { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); ++ const searchEngine = new LunrSearchEngine({ ++ logger: env.logger, ++ }); ++ const indexBuilder = new IndexBuilder({ ++ logger: env.logger, ++ searchEngine, ++ }); ++ ++ const schedule = env.scheduler.createScheduledTaskRunner({ ++ frequency: { minutes: 10 }, ++ timeout: { minutes: 15 }, ++ // A 3 second delay gives the backend server a chance to initialize before ++ // any collators are executed, which may attempt requests against the API. ++ initialDelay: { seconds: 3 }, ++ }); + + // Collators are responsible for gathering documents known to plugins. This +- // particular collator gathers entities from the software catalog. ++ // collator gathers entities from the software catalog. ++ indexBuilder.addCollator({ ++ schedule, ++ factory: DefaultCatalogCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, ++ }), ++ }); ++ ++ // collator gathers entities from techdocs. + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { discovery }), ++ schedule, ++ factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ logger: env.logger, ++ tokenManager: env.tokenManager, ++ }), + }); +@@ -28,6 +54,4 @@ export default async function createPlugin({ + const { scheduler } = await indexBuilder.build(); ++ scheduler.start(); + +- // A 3 second delay gives the backend server a chance to initialize before +- // any collators are executed, which may attempt requests against the API. +- setTimeout(() => scheduler.start(), 3000); + useHotCleanup(module, () => scheduler.stop()); +@@ -36,3 +60,6 @@ export default async function createPlugin({ + engine: indexBuilder.getSearchEngine(), +- logger, ++ types: indexBuilder.getDocumentTypes(), ++ permissions: env.permissions, ++ config: env.config, ++ logger: env.logger, + }); +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +index 906d86d..be8bb0c 100644 +--- a/packages/backend/src/plugins/techdocs.ts ++++ b/packages/backend/src/plugins/techdocs.ts +@@ -11,12 +11,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, ++ const preparers = await Preparers.fromConfig(env.config, { ++ logger: env.logger, ++ reader: env.reader, + }); +@@ -28,4 +25,4 @@ export default async function createPlugin({ + // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, ++ const generators = await Generators.fromConfig(env.config, { ++ logger: env.logger, + containerRunner, +@@ -36,5 +33,5 @@ export default async function createPlugin({ + // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, ++ const publisher = await Publisher.fromConfig(env.config, { ++ logger: env.logger, ++ discovery: env.discovery, + }); +@@ -48,5 +45,6 @@ export default async function createPlugin({ + publisher, +- logger, +- config, +- discovery, ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ cache: env.cache, + }); +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +index 6c78a2a..9cd2c74 100644 +--- a/packages/backend/src/types.ts ++++ b/packages/backend/src/types.ts +@@ -6,4 +6,8 @@ import { + PluginEndpointDiscovery, ++ TokenManager, + UrlReader, + } from '@backstage/backend-common'; ++import { PluginTaskScheduler } from '@backstage/backend-tasks'; ++import { PermissionEvaluator } from '@backstage/plugin-permission-common'; ++import { IdentityApi } from '@backstage/plugin-auth-node'; + +@@ -16,2 +20,6 @@ export type PluginEnvironment = { + discovery: PluginEndpointDiscovery; ++ tokenManager: TokenManager; ++ scheduler: PluginTaskScheduler; ++ permissions: PermissionEvaluator; ++ identity: IdentityApi; + }; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..37c7fb1 +--- /dev/null ++++ b/playwright.config.ts +@@ -0,0 +1,60 @@ ++/* ++ * Copyright 2023 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { defineConfig } from '@playwright/test'; ++import { generateProjects } from '@backstage/e2e-test-utils/playwright'; ++ ++/** ++ * See https://playwright.dev/docs/test-configuration. ++ */ ++export default defineConfig({ ++ timeout: 60_000, ++ ++ expect: { ++ timeout: 5_000, ++ }, ++ ++ // Run your local dev server before starting the tests ++ webServer: process.env.CI ++ ? [] ++ : [ ++ { ++ command: 'yarn start', ++ port: 3000, ++ reuseExistingServer: true, ++ timeout: 60_000, ++ }, ++ ], ++ ++ forbidOnly: !!process.env.CI, ++ ++ retries: process.env.CI ? 2 : 0, ++ ++ reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]], ++ ++ use: { ++ actionTimeout: 0, ++ baseURL: ++ process.env.PLAYWRIGHT_URL ?? ++ (process.env.CI ? 'http://localhost:7007' : 'http://localhost:3000'), ++ screenshot: 'only-on-failure', ++ trace: 'on-first-retry', ++ }, ++ ++ outputDir: 'node_modules/.cache/e2e-test-results', ++ ++ projects: generateProjects(), // Find all packages with e2e-test folders ++}); +diff --git a/plugins/README.md b/plugins/README.md +new file mode 100644 +index 0000000..d7865fd +--- /dev/null ++++ b/plugins/README.md +@@ -0,0 +1,9 @@ ++# The Plugins Folder ++ ++This is where your own plugins and their associated modules live, each in a ++separate folder of its own. ++ ++If you want to create a new plugin here, go to your project root directory, run ++the command `yarn new`, and follow the on-screen instructions. ++ ++You can also check out existing plugins on [the plugin marketplace](https://backstage.io/plugins)! +diff --git a/yarn.lock b/yarn.lock +new file mode 100644 +index 0000000..5ad7fe2 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1 @@ ++# intentionally left empty diff --git a/diffs/0.3.45..0.5.7-next.1.diff b/diffs/0.3.45..0.5.7-next.1.diff new file mode 100644 index 00000000..7be57d69 --- /dev/null +++ b/diffs/0.3.45..0.5.7-next.1.diff @@ -0,0 +1,1931 @@ +diff --git a/.dockerignore b/.dockerignore +index 63c9c34..05edb62 100644 +--- a/.dockerignore ++++ b/.dockerignore +@@ -1,5 +1,8 @@ + .git ++.yarn/cache ++.yarn/install-state.gz + node_modules +-packages +-!packages/backend/dist ++packages/*/src ++packages/*/node_modules + plugins ++*.local.yaml +diff --git a/.eslintignore b/.eslintignore +new file mode 100644 +index 0000000..e5b1994 +--- /dev/null ++++ b/.eslintignore +@@ -0,0 +1 @@ ++playwright.config.ts +diff --git a/.gitignore b/.gitignore +index d16a8d3..fbf8139 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -17,2 +17,11 @@ node_modules/ + ++# Yarn 3 files ++.pnp.* ++.yarn/* ++!.yarn/patches ++!.yarn/plugins ++!.yarn/releases ++!.yarn/sdks ++!.yarn/versions ++ + # Node version directives +@@ -39 +48,7 @@ site + *-credentials.yaml ++ ++# vscode database functionality support files ++*.session.sql ++ ++# E2E test reports ++e2e-test-report/ +diff --git a/app-config.production.yaml b/app-config.production.yaml +index 92f4574..8f0751c 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -1,8 +1,35 @@ + app: +- # Should be the same as backend.baseUrl when using the `app-backend` plugin +- baseUrl: http://localhost:7000 ++ # Should be the same as backend.baseUrl when using the `app-backend` plugin. ++ baseUrl: http://localhost:7007 + + backend: +- baseUrl: http://localhost:7000 +- listen: +- port: 7000 ++ # Note that the baseUrl should be the URL that the browser and other clients ++ # should use when communicating with the backend, i.e. it needs to be ++ # reachable not just from within the backend host, but from all of your ++ # callers. When its value is "http://localhost:7007", it's strictly private ++ # and can't be reached by others. ++ baseUrl: http://localhost:7007 ++ # The listener can also be expressed as a single : string. In this case we bind to ++ # all interfaces, the most permissive setting. The right value depends on your specific deployment. ++ listen: ':7007' ++ ++ # config options: https://node-postgres.com/api/client ++ database: ++ client: pg ++ connection: ++ host: ${POSTGRES_HOST} ++ port: ${POSTGRES_PORT} ++ user: ${POSTGRES_USER} ++ password: ${POSTGRES_PASSWORD} ++ # https://node-postgres.com/features/ssl ++ # you can set the sslmode configuration option via the `PGSSLMODE` environment variable ++ # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require) ++ # ssl: ++ # ca: # if you have a CA file and want to verify it you can uncomment this section ++ # $file: /ca/server.crt ++ ++catalog: ++ # Overrides the default list locations from app-config.yaml as these contain example data. ++ # See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details ++ # on how to get entities into the catalog. ++ locations: [] +diff --git a/app-config.yaml b/app-config.yaml +index 93b0c3f..08d2abf 100644 +--- a/app-config.yaml ++++ b/app-config.yaml +@@ -8,5 +8,13 @@ organization: + backend: +- baseUrl: http://localhost:7000 ++ # Used for enabling authentication, secret is shared by all backend plugins ++ # See https://backstage.io/docs/auth/service-to-service-auth for ++ # information on the format ++ # auth: ++ # keys: ++ # - secret: ${BACKEND_SECRET} ++ baseUrl: http://localhost:7007 + listen: +- port: 7000 ++ port: 7007 ++ # Uncomment the following host directive to bind to specific interfaces ++ # host: 127.0.0.1 + csp: +@@ -17,9 +25,9 @@ backend: + origin: http://localhost:3000 +- methods: [GET, POST, PUT, DELETE] ++ methods: [GET, HEAD, PATCH, POST, PUT, DELETE] + credentials: true ++ # This is for local development only, it is not recommended to use this in production ++ # The production database configuration is stored in app-config.production.yaml + database: +- client: sqlite3 ++ client: better-sqlite3 + connection: ':memory:' +- cache: +- store: memory + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir +@@ -29,2 +37,4 @@ integrations: + - host: github.com ++ # This is a Personal Access Token or PAT from GitHub. You can find out how to generate this token, and more information ++ # about setting up the GitHub integration here: https://backstage.io/docs/getting-started/configuration#setting-up-a-github-integration + token: ${GITHUB_TOKEN} +@@ -36,5 +46,8 @@ integrations: + proxy: +- '/test': +- target: 'https://example.com' +- changeOrigin: true ++ ### Example for how to add a proxy endpoint for the frontend. ++ ### A typical reason to do this is to handle HTTPS and CORS for internal services. ++ # endpoints: ++ # '/test': ++ # target: 'https://example.com' ++ # changeOrigin: true + +@@ -56,46 +69,35 @@ auth: + scaffolder: +- github: +- token: ${GITHUB_TOKEN} +- visibility: public # or 'internal' or 'private' ++ # see https://backstage.io/docs/features/software-templates/configuration for software template options + + catalog: ++ import: ++ entityFilename: catalog-info.yaml ++ pullRequestBranchName: backstage-integration + rules: +- - allow: [Component, System, API, Group, User, Resource, Location] ++ - allow: [Component, System, API, Resource, Location] + locations: +- # Backstage example components +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-components.yaml +- +- # Backstage example systems +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-systems.yaml +- +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml +- +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml ++ # Local example data, file locations are relative to the backend process, typically `packages/backend` ++ - type: file ++ target: ../../examples/entities.yaml + +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.yaml +- +- # Backstage example templates +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/react-ssr-template/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/springboot-grpc-template/template.yaml ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml + rules: + - allow: [Template] +- - type: url +- target: https://github.com/spotify/cookiecutter-golang/blob/master/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/docs-template/template.yaml ++ ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml + rules: +- - allow: [Template] ++ - allow: [User, Group] ++ ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml ++ ++ ## Uncomment these lines to add an example org ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme-corp.yaml ++ # rules: ++ # - allow: [User, Group] +diff --git a/backstage.json b/backstage.json +new file mode 100644 +index 0000000..707af29 +--- /dev/null ++++ b/backstage.json +@@ -0,0 +1,3 @@ ++{ ++ "version": "1.20.0-next.1" ++} +diff --git a/examples/entities.yaml b/examples/entities.yaml +new file mode 100644 +index 0000000..447e8b1 +--- /dev/null ++++ b/examples/entities.yaml +@@ -0,0 +1,41 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system ++apiVersion: backstage.io/v1alpha1 ++kind: System ++metadata: ++ name: examples ++spec: ++ owner: guests ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: example-website ++spec: ++ type: website ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ providesApis: [example-grpc-api] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api ++apiVersion: backstage.io/v1alpha1 ++kind: API ++metadata: ++ name: example-grpc-api ++spec: ++ type: grpc ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ definition: | ++ syntax = "proto3"; ++ ++ service Exampler { ++ rpc Example (ExampleMessage) returns (ExampleMessage) {}; ++ } ++ ++ message ExampleMessage { ++ string example = 1; ++ }; +diff --git a/examples/org.yaml b/examples/org.yaml +new file mode 100644 +index 0000000..a10e81f +--- /dev/null ++++ b/examples/org.yaml +@@ -0,0 +1,17 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user ++apiVersion: backstage.io/v1alpha1 ++kind: User ++metadata: ++ name: guest ++spec: ++ memberOf: [guests] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group ++apiVersion: backstage.io/v1alpha1 ++kind: Group ++metadata: ++ name: guests ++spec: ++ type: team ++ children: [] +diff --git a/examples/template/content/catalog-info.yaml b/examples/template/content/catalog-info.yaml +new file mode 100644 +index 0000000..d4ccca4 +--- /dev/null ++++ b/examples/template/content/catalog-info.yaml +@@ -0,0 +1,8 @@ ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: ${{ values.name | dump }} ++spec: ++ type: service ++ owner: user:guest ++ lifecycle: experimental +diff --git a/examples/template/content/index.js b/examples/template/content/index.js +new file mode 100644 +index 0000000..071ce5a +--- /dev/null ++++ b/examples/template/content/index.js +@@ -0,0 +1 @@ ++console.log('Hello from ${{ values.name }}!'); +diff --git a/examples/template/content/package.json b/examples/template/content/package.json +new file mode 100644 +index 0000000..86f968a +--- /dev/null ++++ b/examples/template/content/package.json +@@ -0,0 +1,5 @@ ++{ ++ "name": "${{ values.name }}", ++ "private": true, ++ "dependencies": {} ++} +diff --git a/examples/template/template.yaml b/examples/template/template.yaml +new file mode 100644 +index 0000000..33f262b +--- /dev/null ++++ b/examples/template/template.yaml +@@ -0,0 +1,74 @@ ++apiVersion: scaffolder.backstage.io/v1beta3 ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template ++kind: Template ++metadata: ++ name: example-nodejs-template ++ title: Example Node.js Template ++ description: An example template for the scaffolder that creates a simple Node.js service ++spec: ++ owner: user:guest ++ type: service ++ ++ # These parameters are used to generate the input form in the frontend, and are ++ # used to gather input data for the execution of the template. ++ parameters: ++ - title: Fill in some steps ++ required: ++ - name ++ properties: ++ name: ++ title: Name ++ type: string ++ description: Unique name of the component ++ ui:autofocus: true ++ ui:options: ++ rows: 5 ++ - title: Choose a location ++ required: ++ - repoUrl ++ properties: ++ repoUrl: ++ title: Repository Location ++ type: string ++ ui:field: RepoUrlPicker ++ ui:options: ++ allowedHosts: ++ - github.com ++ ++ # These steps are executed in the scaffolder backend, using data that we gathered ++ # via the parameters above. ++ steps: ++ # Each step executes an action, in this case one templates files into the working directory. ++ - id: fetch-base ++ name: Fetch Base ++ action: fetch:template ++ input: ++ url: ./content ++ values: ++ name: ${{ parameters.name }} ++ ++ # This step publishes the contents of the working directory to GitHub. ++ - id: publish ++ name: Publish ++ action: publish:github ++ input: ++ allowedHosts: ['github.com'] ++ description: This is ${{ parameters.name }} ++ repoUrl: ${{ parameters.repoUrl }} ++ ++ # The final step is to register our new component in the catalog. ++ - id: register ++ name: Register ++ action: catalog:register ++ input: ++ repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} ++ catalogInfoPath: '/catalog-info.yaml' ++ ++ # Outputs are displayed to the user after a successful execution of the template. ++ output: ++ links: ++ - title: Repository ++ url: ${{ steps['publish'].output.remoteUrl }} ++ - title: Open in catalog ++ icon: catalog ++ entityRef: ${{ steps['register'].output.entityRef }} +diff --git a/lerna.json b/lerna.json +index 322929d..529a62f 100644 +--- a/lerna.json ++++ b/lerna.json +@@ -3,4 +3,4 @@ + "npmClient": "yarn", +- "useWorkspaces": true, +- "version": "0.1.0" ++ "version": "0.1.0", ++ "$schema": "node_modules/lerna/schemas/lerna-schema.json" + } +diff --git a/package.json b/package.json +index 9d9601f..2364da8 100644 +--- a/package.json ++++ b/package.json +@@ -5,3 +5,3 @@ + "engines": { +- "node": "14 || 16" ++ "node": "18 || 20" + }, +@@ -11,3 +11,4 @@ + "start-backend": "yarn workspace backend start", +- "build": "lerna run build", ++ "build:backend": "yarn workspace backend build", ++ "build:all": "backstage-cli repo build --all", + "build-image": "yarn workspace backend build-image", +@@ -15,15 +16,11 @@ + "tsc:full": "tsc --skipLibCheck false --incremental false", +- "clean": "backstage-cli clean && lerna run clean", +- "diff": "lerna run diff --", +- "test": "backstage-cli test", +- "test:all": "lerna run test -- --coverage", +- "lint": "lerna run lint --since origin/master --", +- "lint:all": "lerna run lint --", ++ "clean": "backstage-cli repo clean", ++ "test": "backstage-cli repo test", ++ "test:all": "backstage-cli repo test --coverage", ++ "test:e2e": "playwright test", ++ "fix": "backstage-cli repo fix", ++ "lint": "backstage-cli repo lint --since origin/master", ++ "lint:all": "backstage-cli repo lint", + "prettier:check": "prettier --check .", +- "create-plugin": "backstage-cli create-plugin --scope internal --no-private", +- "remove-plugin": "backstage-cli remove-plugin" +- }, +- "resolutions": { +- "graphql-language-service-interface": "2.8.2", +- "graphql-language-service-parser": "1.9.0" ++ "new": "backstage-cli new --scope internal" + }, +@@ -36,7 +33,15 @@ + "devDependencies": { +- "@backstage/cli": "^0.7.16", +- "@spotify/prettier-config": "^11.0.0", +- "concurrently": "^6.0.0", +- "lerna": "^4.0.0", +- "prettier": "^2.3.2" ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/e2e-test-utils": "^0.1.0", ++ "@playwright/test": "^1.32.3", ++ "@spotify/prettier-config": "^12.0.0", ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^9.0.0", ++ "prettier": "^2.3.2", ++ "typescript": "~5.2.0" ++ }, ++ "resolutions": { ++ "@types/react": "^17", ++ "@types/react-dom": "^17" + }, +@@ -44,3 +49,3 @@ + "lint-staged": { +- "*.{js,jsx,ts,tsx}": [ ++ "*.{js,jsx,ts,tsx,mjs,cjs}": [ + "eslint --fix", +@@ -51,7 +56,2 @@ + ] +- }, +- "jest": { +- "transformModules": [ +- "@asyncapi/react-component" +- ] + } +diff --git a/packages/README.md b/packages/README.md +new file mode 100644 +index 0000000..6327fa0 +--- /dev/null ++++ b/packages/README.md +@@ -0,0 +1,9 @@ ++# The Packages Folder ++ ++This is where your own applications and centrally managed libraries live, each ++in a separate folder of its own. ++ ++From the start there's an `app` folder (for the frontend) and a `backend` folder ++(for the Node backend), but you can also add more modules in here that house ++your core additions and adaptations, such as themes, common React component ++libraries, utilities, and similar. +diff --git a/packages/app/.eslintignore b/packages/app/.eslintignore +new file mode 100644 +index 0000000..a48cf0d +--- /dev/null ++++ b/packages/app/.eslintignore +@@ -0,0 +1 @@ ++public +diff --git a/packages/app/.eslintrc.js b/packages/app/.eslintrc.js +index 13573ef..e2a53a6 100644 +--- a/packages/app/.eslintrc.js ++++ b/packages/app/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/app/cypress.json b/packages/app/cypress.json +deleted file mode 100644 +index 5de7ebf..0000000 +--- a/packages/app/cypress.json ++++ /dev/null +@@ -1,5 +0,0 @@ +-{ +- "baseUrl": "http://localhost:3001", +- "fixturesFolder": false, +- "pluginsFile": false +-} +diff --git a/packages/app/cypress/.eslintrc.json b/packages/app/cypress/.eslintrc.json +deleted file mode 100644 +index 2b3a458..0000000 +--- a/packages/app/cypress/.eslintrc.json ++++ /dev/null +@@ -1,21 +0,0 @@ +-{ +- "plugins": ["cypress"], +- "extends": ["plugin:cypress/recommended"], +- "rules": { +- "jest/expect-expect": [ +- "error", +- { +- "assertFunctionNames": ["expect", "cy.contains"] +- } +- ], +- "import/no-extraneous-dependencies": [ +- "error", +- { +- "devDependencies": true, +- "optionalDependencies": true, +- "peerDependencies": true, +- "bundledDependencies": true +- } +- ] +- } +-} +diff --git a/packages/app/cypress/integration/app.js b/packages/app/cypress/integration/app.js +deleted file mode 100644 +index 43fb2e3..0000000 +--- a/packages/app/cypress/integration/app.js ++++ /dev/null +@@ -1,6 +0,0 @@ +-describe('App', () => { +- it('should render the catalog', () => { +- cy.visit('/'); +- cy.contains('My Company Catalog'); +- }); +-}); +diff --git a/packages/app/e2e-tests/app.test.ts b/packages/app/e2e-tests/app.test.ts +new file mode 100644 +index 0000000..d45bc0d +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,23 @@ ++/* ++ * Copyright 2020 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { test, expect } from '@playwright/test'; ++ ++test('App should render the welcome page', async ({ page }) => { ++ await page.goto('/'); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index a860127..2100405 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,22 +5,38 @@ + "bundled": true, ++ "backstage": { ++ "role": "frontend" ++ }, ++ "scripts": { ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "clean": "backstage-cli package clean", ++ "test": "backstage-cli package test", ++ "lint": "backstage-cli package lint" ++ }, + "dependencies": { +- "@backstage/catalog-model": "^0.9.4", +- "@backstage/cli": "^0.7.16", +- "@backstage/core-app-api": "^0.1.17", +- "@backstage/core-components": "^0.7.0", +- "@backstage/core-plugin-api": "^0.1.10", +- "@backstage/integration-react": "^0.1.12", +- "@backstage/plugin-api-docs": "^0.6.12", +- "@backstage/plugin-catalog": "^0.7.1", +- "@backstage/plugin-catalog-import": "^0.7.2", +- "@backstage/plugin-catalog-react": "^0.6.0", +- "@backstage/plugin-github-actions": "^0.4.22", +- "@backstage/plugin-org": "^0.3.27", +- "@backstage/plugin-scaffolder": "^0.11.8", +- "@backstage/plugin-search": "^0.4.15", +- "@backstage/plugin-tech-radar": "^0.4.11", +- "@backstage/plugin-techdocs": "^0.12.2", +- "@backstage/plugin-user-settings": "^0.3.9", +- "@backstage/test-utils": "^0.1.19", +- "@backstage/theme": "^0.2.11", ++ "@backstage/app-defaults": "^1.4.5-next.1", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/core-app-api": "^1.11.1-next.0", ++ "@backstage/core-components": "^0.13.8-next.1", ++ "@backstage/core-plugin-api": "^1.8.0-next.0", ++ "@backstage/integration-react": "^1.1.21-next.1", ++ "@backstage/plugin-api-docs": "^0.9.13-next.1", ++ "@backstage/plugin-catalog": "^1.15.0-next.1", ++ "@backstage/plugin-catalog-common": "^1.0.17", ++ "@backstage/plugin-catalog-graph": "^0.2.38-next.1", ++ "@backstage/plugin-catalog-import": "^0.10.2-next.1", ++ "@backstage/plugin-catalog-react": "^1.9.0-next.1", ++ "@backstage/plugin-github-actions": "^0.6.7-next.1", ++ "@backstage/plugin-org": "^0.6.16-next.1", ++ "@backstage/plugin-permission-react": "^0.4.17-next.0", ++ "@backstage/plugin-scaffolder": "^1.16.0-next.1", ++ "@backstage/plugin-search": "^1.4.2-next.1", ++ "@backstage/plugin-search-react": "^1.7.2-next.1", ++ "@backstage/plugin-tech-radar": "^0.6.10-next.1", ++ "@backstage/plugin-techdocs": "^1.8.1-next.1", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.2-next.1", ++ "@backstage/plugin-techdocs-react": "^1.1.13-next.1", ++ "@backstage/plugin-user-settings": "^0.7.12-next.1", ++ "@backstage/theme": "^0.4.4-next.0", + "@material-ui/core": "^4.12.2", +@@ -28,30 +44,17 @@ + "history": "^5.0.0", +- "react": "^16.13.1", +- "react-dom": "^16.13.1", +- "react-router": "6.0.0-beta.0", +- "react-router-dom": "6.0.0-beta.0", +- "react-use": "^15.3.3" ++ "react": "^17.0.2", ++ "react-dom": "^17.0.2", ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { ++ "@backstage/test-utils": "^1.4.5-next.0", ++ "@playwright/test": "^1.32.3", + "@testing-library/jest-dom": "^5.10.1", +- "@testing-library/react": "^10.4.1", +- "@testing-library/user-event": "^12.0.7", +- "@types/jest": "^26.0.7", +- "@types/node": "^14.14.32", ++ "@testing-library/react": "^12.1.3", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^8.0.0", + "@types/react-dom": "*", +- "cross-env": "^7.0.0", +- "cypress": "^7.3.0", +- "eslint-plugin-cypress": "^2.10.3", +- "start-server-and-test": "^1.10.11" +- }, +- "scripts": { +- "start": "backstage-cli app:serve", +- "build": "backstage-cli app:build", +- "test": "backstage-cli test", +- "lint": "backstage-cli lint", +- "clean": "backstage-cli clean", +- "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", +- "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", +- "cy:dev": "cypress open", +- "cy:run": "cypress run" ++ "cross-env": "^7.0.0" + }, +diff --git a/packages/app/public/index.html b/packages/app/public/index.html +index b93896c..fc758ee 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -10,3 +10,2 @@ + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index 82bc479..b94cac7 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -12,5 +12,5 @@ describe('App', () => { + app: { title: 'Test' }, +- backend: { baseUrl: 'http://localhost:7000' }, ++ backend: { baseUrl: 'http://localhost:7007' }, + techdocs: { +- storageUrl: 'http://localhost:7000/api/techdocs/static/docs', ++ storageUrl: 'http://localhost:7007/api/techdocs/static/docs', + }, +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 4cd8368..8d62f29 100644 +--- a/packages/app/src/App.tsx ++++ b/packages/app/src/App.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { Navigate, Route } from 'react-router'; ++import { Navigate, Route } from 'react-router-dom'; + import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; +@@ -13,2 +13,3 @@ import { + import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; ++import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +@@ -16,3 +17,2 @@ import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -21,2 +21,4 @@ import { + } from '@backstage/plugin-techdocs'; ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; + import { UserSettingsPage } from '@backstage/plugin-user-settings'; +@@ -28,3 +30,7 @@ import { Root } from './components/Root'; + import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; +-import { createApp, FlatRoutes } from '@backstage/core-app-api'; ++import { createApp } from '@backstage/app-defaults'; ++import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; ++import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; ++import { RequirePermission } from '@backstage/plugin-permission-react'; ++import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; + +@@ -36,5 +42,6 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); + bind(apiDocsPlugin.externalRoutes, { +- createComponent: scaffolderPlugin.routes.root, ++ registerApi: catalogImportPlugin.routes.importPage, + }); +@@ -42,2 +49,6 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, ++ }); ++ bind(orgPlugin.externalRoutes, { ++ catalogIndex: catalogPlugin.routes.catalogIndex, + }); +@@ -46,8 +57,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -59,5 +67,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -72,3 +82,10 @@ const routes = ( + /> +- } /> ++ ++ ++ ++ } ++ /> + }> +@@ -77,2 +94,3 @@ const routes = ( + } /> ++ } /> + +@@ -80,4 +98,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -87,5 +105,3 @@ const App = () => ( + +- ++ , + ); +- +-export default App; +diff --git a/packages/app/src/components/Root/LogoFull.tsx b/packages/app/src/components/Root/LogoFull.tsx +index c7b1c84..47e3b73 100644 +--- a/packages/app/src/components/Root/LogoFull.tsx ++++ b/packages/app/src/components/Root/LogoFull.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/LogoIcon.tsx b/packages/app/src/components/Root/LogoIcon.tsx +index 073cf6e..7eae8c7 100644 +--- a/packages/app/src/components/Root/LogoIcon.tsx ++++ b/packages/app/src/components/Root/LogoIcon.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx +index 198e7ec..6768b48 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,19 +1,3 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- +-import React, { useContext, PropsWithChildren } from 'react'; +-import { Link, makeStyles } from '@material-ui/core'; ++import React, { PropsWithChildren } from 'react'; ++import { makeStyles } from '@material-ui/core'; + import HomeIcon from '@material-ui/icons/Home'; +@@ -25,15 +9,21 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; +-import { Settings as SidebarSettings } from '@backstage/plugin-user-settings'; +-import { SidebarSearch } from '@backstage/plugin-search'; ++import { ++ Settings as SidebarSettings, ++ UserSettingsSignInAvatar, ++} from '@backstage/plugin-user-settings'; ++import { SidebarSearchModal } from '@backstage/plugin-search'; + import { + Sidebar, +- SidebarPage, + sidebarConfig, +- SidebarContext, +- SidebarItem, + SidebarDivider, +- SidebarSpace, ++ SidebarGroup, ++ SidebarItem, ++ SidebarPage, + SidebarScrollWrapper, ++ SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; ++import MenuIcon from '@material-ui/icons/Menu'; ++import SearchIcon from '@material-ui/icons/Search'; + +@@ -56,3 +46,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -60,8 +50,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -76,17 +61,27 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ } to="/search"> ++ ++ + +- {/* Global nav, not org-specific */} +- +- +- +- +- {/* End global nav */} +- +- +- +- ++ }> ++ {/* Global nav, not org-specific */} ++ ++ ++ ++ ++ {/* End global nav */} ++ ++ ++ ++ ++ + + +- ++ } ++ to="/settings" ++ > ++ ++ + +diff --git a/packages/app/src/components/Root/index.ts b/packages/app/src/components/Root/index.ts +index dff706f..3528881 100644 +--- a/packages/app/src/components/Root/index.ts ++++ b/packages/app/src/components/Root/index.ts +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + export { Root } from './Root'; +diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx +index d3b4b78..6722ea2 100644 +--- a/packages/app/src/components/catalog/EntityPage.tsx ++++ b/packages/app/src/components/catalog/EntityPage.tsx +@@ -1,16 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ + import React from 'react'; +@@ -29,3 +14,2 @@ import { + EntityDependsOnResourcesCard, +- EntitySystemDiagramCard, + EntityHasComponentsCard, +@@ -43,2 +27,4 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +@@ -56,2 +42,27 @@ import { EntityTechdocsContent } from '@backstage/plugin-techdocs'; + import { EmptyState } from '@backstage/core-components'; ++import { ++ Direction, ++ EntityCatalogGraphCard, ++} from '@backstage/plugin-catalog-graph'; ++import { ++ RELATION_API_CONSUMED_BY, ++ RELATION_API_PROVIDED_BY, ++ RELATION_CONSUMES_API, ++ RELATION_DEPENDENCY_OF, ++ RELATION_DEPENDS_ON, ++ RELATION_HAS_PART, ++ RELATION_PART_OF, ++ RELATION_PROVIDES_API, ++} from '@backstage/catalog-model'; ++ ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); + +@@ -94,2 +105,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -110,2 +129,6 @@ const overviewContent = ( + ++ ++ ++ ++ + +@@ -152,3 +175,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -179,3 +202,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -198,3 +221,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -225,2 +248,5 @@ const apiPage = ( + ++ ++ ++ + +@@ -292,3 +318,9 @@ const systemPage = ( + +- ++ ++ ++ ++ ++ ++ ++ + +@@ -304,3 +336,19 @@ const systemPage = ( + +- ++ + +@@ -317,2 +365,5 @@ const domainPage = ( + ++ ++ ++ + +diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx +index 7b3c2b2..1788dde 100644 +--- a/packages/app/src/components/search/SearchPage.tsx ++++ b/packages/app/src/components/search/SearchPage.tsx +@@ -1,5 +1,12 @@ + import React from 'react'; +-import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; ++import { makeStyles, Theme, Grid, Paper } from '@material-ui/core'; + +-import { CatalogResultListItem } from '@backstage/plugin-catalog'; ++import { CatalogSearchResultListItem } from '@backstage/plugin-catalog'; ++import { ++ catalogApiRef, ++ CATALOG_FILTER_EXISTS, ++} from '@backstage/plugin-catalog-react'; ++import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; ++ ++import { SearchType } from '@backstage/plugin-search'; + import { +@@ -8,5 +15,13 @@ import { + SearchResult, +- DefaultResultListItem, +-} from '@backstage/plugin-search'; +-import { Content, Header, Page } from '@backstage/core-components'; ++ SearchPagination, ++ useSearch, ++} from '@backstage/plugin-search-react'; ++import { ++ CatalogIcon, ++ Content, ++ DocsIcon, ++ Header, ++ Page, ++} from '@backstage/core-components'; ++import { useApi } from '@backstage/core-plugin-api'; + +@@ -18,2 +33,3 @@ const useStyles = makeStyles((theme: Theme) => ({ + padding: theme.spacing(2), ++ marginTop: theme.spacing(2), + }, +@@ -28,2 +44,4 @@ const SearchPage = () => { + const classes = useStyles(); ++ const { types } = useSearch(); ++ const catalogApi = useApi(catalogApiRef); + +@@ -36,3 +54,3 @@ const SearchPage = () => { + +- ++ + +@@ -40,5 +58,43 @@ const SearchPage = () => { + ++ , ++ }, ++ { ++ value: 'techdocs', ++ name: 'Documentation', ++ icon: , ++ }, ++ ]} ++ /> + ++ {types.includes('techdocs') && ( ++ { ++ // Return a list of entities which are documented. ++ const { items } = await catalogApi.getEntities({ ++ fields: ['metadata.name'], ++ filter: { ++ 'metadata.annotations.backstage.io/techdocs-ref': ++ CATALOG_FILTER_EXISTS, ++ }, ++ }); ++ ++ const names = items.map(entity => entity.metadata.name); ++ names.sort(); ++ return names; ++ }} ++ /> ++ )} + { + className={classes.filter} ++ label="Lifecycle" + name="lifecycle" +@@ -54,25 +111,6 @@ const SearchPage = () => { + ++ + +- {({ results }) => ( +- +- {results.map(({ type, document }) => { +- switch (type) { +- case 'software-catalog': +- return ( +- +- ); +- default: +- return ( +- +- ); +- } +- })} +- +- )} ++ } /> ++ } /> + +diff --git a/packages/backend/.eslintrc.js b/packages/backend/.eslintrc.js +index 16a033d..e2a53a6 100644 +--- a/packages/backend/.eslintrc.js ++++ b/packages/backend/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint.backend')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index 31231a3..18548e9 100644 +--- a/packages/backend/Dockerfile ++++ b/packages/backend/Dockerfile +@@ -7,3 +7,3 @@ + # yarn tsc +-# yarn build ++# yarn build:backend + # +@@ -11,6 +11,29 @@ + +-FROM node:14-buster-slim ++FROM node:18-bookworm-slim + ++# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends python3 g++ build-essential && \ ++ yarn config set python /usr/bin/python3 ++ ++# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, ++# in which case you should also move better-sqlite3 to "devDependencies" in package.json. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends libsqlite3-dev ++ ++# From here on we use the least-privileged `node` user to run the backend. ++USER node ++ ++# This should create the app dir as `node`. ++# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. ++# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. + WORKDIR /app + ++# This switches many Node.js dependencies to production mode. ++ENV NODE_ENV production ++ + # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. +@@ -18,11 +41,12 @@ WORKDIR /app + # and along with yarn.lock and the root package.json, that's enough to run yarn install. +-COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ ++COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ + RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz + +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + + # Then copy the rest of the backend bundle, along with any other files we might want. +-COPY packages/backend/dist/bundle.tar.gz app-config.yaml ./ ++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"] +diff --git a/packages/backend/README.md b/packages/backend/README.md +index 81e0f80..3607b0a 100644 +--- a/packages/backend/README.md ++++ b/packages/backend/README.md +@@ -18,4 +18,2 @@ To run the example backend, first go to the project root and run + yarn install +-yarn tsc +-yarn build + ``` +@@ -27,7 +25,2 @@ After that, go to the `packages/backend` directory and run + ```bash +-AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x \ +-AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x \ +-AUTH_OAUTH2_CLIENT_ID=x AUTH_OAUTH2_CLIENT_SECRET=x \ +-AUTH_OAUTH2_AUTH_URL=x AUTH_OAUTH2_TOKEN_URL=x \ +-LOG_LEVEL=debug \ + yarn start +@@ -35,6 +28,6 @@ yarn start + +-Substitute `x` for actual values, or leave them as dummy values just to try out +-the backend without using the auth or sentry features. ++If you want to override any configuration locally, for example adding any secrets, ++you can do so in `app-config.local.yaml`. + +-The backend starts up on port 7000 per default. ++The backend starts up on port 7007 per default. + +@@ -45,3 +38,3 @@ locations to the backend. These are places where the backend can find some + entity descriptor data to consume and serve. For more information, see +-[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog). ++[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). + +@@ -65,2 +58,2 @@ and + - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +-- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) ++- [Backstage Documentation](https://backstage.io/docs) +diff --git a/packages/backend/package.json b/packages/backend/package.json +index a343d53..f224eaf 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,32 +6,37 @@ + "private": true, ++ "backstage": { ++ "role": "backend" ++ }, + "scripts": { +- "build": "backstage-cli backend:bundle", +- "build-image": "docker build ../.. -f Dockerfile --tag backstage", +- "start": "backstage-cli backend:dev", +- "lint": "backstage-cli lint", +- "test": "backstage-cli test", +- "clean": "backstage-cli clean", +- "migrate:create": "knex migrate:make -x ts" ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "lint": "backstage-cli package lint", ++ "test": "backstage-cli package test", ++ "clean": "backstage-cli package clean", ++ "build-image": "docker build ../.. -f Dockerfile --tag backstage" + }, + "dependencies": { +- "app": "0.0.0", +- "@backstage/backend-common": "^0.9.6", +- "@backstage/catalog-model": "^0.9.4", +- "@backstage/catalog-client": "^0.5.0", +- "@backstage/config": "^0.1.10", +- "@backstage/plugin-app-backend": "^0.3.16", +- "@backstage/plugin-auth-backend": "^0.4.4", +- "@backstage/plugin-catalog-backend": "^0.17.0", +- "@backstage/plugin-proxy-backend": "^0.2.13", +- "@backstage/plugin-scaffolder-backend": "^0.15.8", +- "@backstage/plugin-search-backend": "^0.2.6", +- "@backstage/plugin-search-backend-node": "^0.4.2", +- "@backstage/plugin-techdocs-backend": "^0.10.4", +- "@gitbeaker/node": "^30.2.0", +- "@octokit/rest": "^18.5.3", +- "dockerode": "^3.2.1", ++ "@backstage/backend-common": "^0.19.9-next.1", ++ "@backstage/backend-tasks": "^0.5.12-next.1", ++ "@backstage/catalog-client": "^1.4.5", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/config": "^1.1.1", ++ "@backstage/plugin-app-backend": "^0.3.55-next.1", ++ "@backstage/plugin-auth-backend": "^0.20.0-next.1", ++ "@backstage/plugin-auth-node": "^0.4.1-next.1", ++ "@backstage/plugin-catalog-backend": "^1.15.0-next.1", ++ "@backstage/plugin-permission-common": "^0.7.9", ++ "@backstage/plugin-permission-node": "^0.7.18-next.1", ++ "@backstage/plugin-proxy-backend": "^0.4.5-next.1", ++ "@backstage/plugin-scaffolder-backend": "^1.19.0-next.1", ++ "@backstage/plugin-search-backend": "^1.4.7-next.1", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.16-next.1", ++ "@backstage/plugin-search-backend-node": "^1.2.11-next.1", ++ "@backstage/plugin-techdocs-backend": "^1.9.0-next.1", ++ "app": "link:../app", ++ "better-sqlite3": "^8.0.0", ++ "dockerode": "^3.3.1", + "express": "^4.17.1", + "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "pg": "^8.3.0", + "winston": "^3.2.1" +@@ -39,6 +44,7 @@ + "devDependencies": { +- "@backstage/cli": "^0.7.16", +- "@types/dockerode": "^3.2.1", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@types/dockerode": "^3.3.0", + "@types/express": "^4.17.6", +- "@types/express-serve-static-core": "^4.17.5" ++ "@types/express-serve-static-core": "^4.17.5", ++ "@types/luxon": "^2.0.4" + }, +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index f2b14b2..04c4ff9 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -17,5 +17,7 @@ import { + DatabaseManager, +- SingleHostDiscovery, ++ HostDiscovery, + UrlReaders, ++ ServerTokenManager, + } from '@backstage/backend-common'; ++import { TaskScheduler } from '@backstage/backend-tasks'; + import { Config } from '@backstage/config'; +@@ -29,2 +31,4 @@ import search from './plugins/search'; + import { PluginEnvironment } from './types'; ++import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; + +@@ -33,8 +37,17 @@ function makeCreateEnv(config: Config) { + const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++ const discovery = HostDiscovery.fromConfig(config); ++ const cacheManager = CacheManager.fromConfig(config); ++ const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); ++ const tokenManager = ServerTokenManager.noop(); ++ const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); + +- root.info(`Created UrlReader ${reader}`); ++ const identity = DefaultIdentityClient.create({ ++ discovery, ++ }); ++ const permissions = ServerPermissionClient.fromConfig(config, { ++ discovery, ++ tokenManager, ++ }); + +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++ root.info(`Created UrlReader ${reader}`); + +@@ -44,3 +57,15 @@ function makeCreateEnv(config: Config) { + const cache = cacheManager.forPlugin(plugin); +- return { logger, database, cache, config, reader, discovery }; ++ const scheduler = taskScheduler.forPlugin(plugin); ++ return { ++ logger, ++ database, ++ cache, ++ config, ++ reader, ++ discovery, ++ tokenManager, ++ scheduler, ++ permissions, ++ identity, ++ }; + }; +@@ -70,2 +95,4 @@ async function main() { + apiRouter.use('/search', await search(searchEnv)); ++ ++ // Add backends ABOVE this line; this 404 handler is the catch-all fallback + apiRouter.use(notFoundHandler()); +@@ -85,3 +112,3 @@ module.hot?.accept(); + main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); ++ console.error('Backend failed to start up', error); + process.exit(1); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +index 07fb04f..7c37f68 100644 +--- a/packages/backend/src/plugins/app.ts ++++ b/packages/backend/src/plugins/app.ts +@@ -4,9 +4,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + return await createRouter({ +- logger, +- config, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, + appPackageName: 'app', +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +index 5216510..77eb6aa 100644 +--- a/packages/backend/src/plugins/auth.ts ++++ b/packages/backend/src/plugins/auth.ts +@@ -1,2 +1,6 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; ++import { ++ createRouter, ++ providers, ++ defaultAuthProviderFactories, ++} from '@backstage/plugin-auth-backend'; + import { Router } from 'express'; +@@ -4,9 +8,47 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, database, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, ++ providerFactories: { ++ ...defaultAuthProviderFactories, ++ ++ // This replaces the default GitHub auth provider with a customized one. ++ // The `signIn` option enables sign-in for this provider, using the ++ // identity resolution logic that's provided in the `resolver` callback. ++ // ++ // This particular resolver makes all users share a single "guest" identity. ++ // It should only be used for testing and trying out Backstage. ++ // ++ // If you want to use a production ready resolver you can switch to ++ // the one that is commented out below, it looks up a user entity in the ++ // catalog using the GitHub username of the authenticated user. ++ // That resolver requires you to have user entities populated in the catalog, ++ // for example using https://backstage.io/docs/integrations/github/org ++ // ++ // There are other resolvers to choose from, and you can also create ++ // your own, see the auth documentation for more details: ++ // ++ // https://backstage.io/docs/auth/identity-resolver ++ github: providers.github.create({ ++ signIn: { ++ resolver(_, ctx) { ++ const userRef = 'user:default/guest'; // Must be a full entity reference ++ return ctx.issueToken({ ++ claims: { ++ sub: userRef, // The user's own identity ++ ent: [userRef], // A list of identities that the user claims ownership through ++ }, ++ }); ++ }, ++ // resolver: providers.github.resolvers.usernameMatchingUserEntityName(), ++ }, ++ }), ++ }, ++ }); + } +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +index 506f6d9..54ec393 100644 +--- a/packages/backend/src/plugins/proxy.ts ++++ b/packages/backend/src/plugins/proxy.ts +@@ -4,8 +4,10 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ }); + } +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +index 6be2e97..a12fee2 100644 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ b/packages/backend/src/plugins/scaffolder.ts +@@ -1,5 +1,3 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; + import { CatalogClient } from '@backstage/catalog-client'; + import { createRouter } from '@backstage/plugin-scaffolder-backend'; +-import Docker from 'dockerode'; + import { Router } from 'express'; +@@ -7,20 +5,17 @@ import type { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- database, +- reader, +- discovery, +-}: PluginEnvironment): Promise { +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- const catalogClient = new CatalogClient({ discoveryApi: discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ const catalogClient = new CatalogClient({ ++ discoveryApi: env.discovery, ++ }); + + return await createRouter({ +- containerRunner, +- logger, +- config, +- database, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ reader: env.reader, + catalogClient, +- reader, ++ identity: env.identity, ++ permissions: env.permissions, + }); +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +index 7fc317d..e9469dc 100644 +--- a/packages/backend/src/plugins/search.ts ++++ b/packages/backend/src/plugins/search.ts +@@ -7,18 +7,44 @@ import { + import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; ++import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend'; ++import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; ++import { Router } from 'express'; + +-export default async function createPlugin({ +- logger, +- discovery, +- config, +-}: PluginEnvironment) { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); ++ const searchEngine = new LunrSearchEngine({ ++ logger: env.logger, ++ }); ++ const indexBuilder = new IndexBuilder({ ++ logger: env.logger, ++ searchEngine, ++ }); ++ ++ const schedule = env.scheduler.createScheduledTaskRunner({ ++ frequency: { minutes: 10 }, ++ timeout: { minutes: 15 }, ++ // A 3 second delay gives the backend server a chance to initialize before ++ // any collators are executed, which may attempt requests against the API. ++ initialDelay: { seconds: 3 }, ++ }); + + // Collators are responsible for gathering documents known to plugins. This +- // particular collator gathers entities from the software catalog. ++ // collator gathers entities from the software catalog. ++ indexBuilder.addCollator({ ++ schedule, ++ factory: DefaultCatalogCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, ++ }), ++ }); ++ ++ // collator gathers entities from techdocs. + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { discovery }), ++ schedule, ++ factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ logger: env.logger, ++ tokenManager: env.tokenManager, ++ }), + }); +@@ -28,6 +54,4 @@ export default async function createPlugin({ + const { scheduler } = await indexBuilder.build(); ++ scheduler.start(); + +- // A 3 second delay gives the backend server a chance to initialize before +- // any collators are executed, which may attempt requests against the API. +- setTimeout(() => scheduler.start(), 3000); + useHotCleanup(module, () => scheduler.stop()); +@@ -36,3 +60,6 @@ export default async function createPlugin({ + engine: indexBuilder.getSearchEngine(), +- logger, ++ types: indexBuilder.getDocumentTypes(), ++ permissions: env.permissions, ++ config: env.config, ++ logger: env.logger, + }); +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +index 906d86d..be8bb0c 100644 +--- a/packages/backend/src/plugins/techdocs.ts ++++ b/packages/backend/src/plugins/techdocs.ts +@@ -11,12 +11,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, ++ const preparers = await Preparers.fromConfig(env.config, { ++ logger: env.logger, ++ reader: env.reader, + }); +@@ -28,4 +25,4 @@ export default async function createPlugin({ + // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, ++ const generators = await Generators.fromConfig(env.config, { ++ logger: env.logger, + containerRunner, +@@ -36,5 +33,5 @@ export default async function createPlugin({ + // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, ++ const publisher = await Publisher.fromConfig(env.config, { ++ logger: env.logger, ++ discovery: env.discovery, + }); +@@ -48,5 +45,6 @@ export default async function createPlugin({ + publisher, +- logger, +- config, +- discovery, ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ cache: env.cache, + }); +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +index 6c78a2a..9cd2c74 100644 +--- a/packages/backend/src/types.ts ++++ b/packages/backend/src/types.ts +@@ -6,4 +6,8 @@ import { + PluginEndpointDiscovery, ++ TokenManager, + UrlReader, + } from '@backstage/backend-common'; ++import { PluginTaskScheduler } from '@backstage/backend-tasks'; ++import { PermissionEvaluator } from '@backstage/plugin-permission-common'; ++import { IdentityApi } from '@backstage/plugin-auth-node'; + +@@ -16,2 +20,6 @@ export type PluginEnvironment = { + discovery: PluginEndpointDiscovery; ++ tokenManager: TokenManager; ++ scheduler: PluginTaskScheduler; ++ permissions: PermissionEvaluator; ++ identity: IdentityApi; + }; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..37c7fb1 +--- /dev/null ++++ b/playwright.config.ts +@@ -0,0 +1,60 @@ ++/* ++ * Copyright 2023 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { defineConfig } from '@playwright/test'; ++import { generateProjects } from '@backstage/e2e-test-utils/playwright'; ++ ++/** ++ * See https://playwright.dev/docs/test-configuration. ++ */ ++export default defineConfig({ ++ timeout: 60_000, ++ ++ expect: { ++ timeout: 5_000, ++ }, ++ ++ // Run your local dev server before starting the tests ++ webServer: process.env.CI ++ ? [] ++ : [ ++ { ++ command: 'yarn start', ++ port: 3000, ++ reuseExistingServer: true, ++ timeout: 60_000, ++ }, ++ ], ++ ++ forbidOnly: !!process.env.CI, ++ ++ retries: process.env.CI ? 2 : 0, ++ ++ reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]], ++ ++ use: { ++ actionTimeout: 0, ++ baseURL: ++ process.env.PLAYWRIGHT_URL ?? ++ (process.env.CI ? 'http://localhost:7007' : 'http://localhost:3000'), ++ screenshot: 'only-on-failure', ++ trace: 'on-first-retry', ++ }, ++ ++ outputDir: 'node_modules/.cache/e2e-test-results', ++ ++ projects: generateProjects(), // Find all packages with e2e-test folders ++}); +diff --git a/plugins/README.md b/plugins/README.md +new file mode 100644 +index 0000000..d7865fd +--- /dev/null ++++ b/plugins/README.md +@@ -0,0 +1,9 @@ ++# The Plugins Folder ++ ++This is where your own plugins and their associated modules live, each in a ++separate folder of its own. ++ ++If you want to create a new plugin here, go to your project root directory, run ++the command `yarn new`, and follow the on-screen instructions. ++ ++You can also check out existing plugins on [the plugin marketplace](https://backstage.io/plugins)! +diff --git a/yarn.lock b/yarn.lock +new file mode 100644 +index 0000000..5ad7fe2 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1 @@ ++# intentionally left empty diff --git a/diffs/0.4.0..0.5.7-next.1.diff b/diffs/0.4.0..0.5.7-next.1.diff new file mode 100644 index 00000000..06cf7575 --- /dev/null +++ b/diffs/0.4.0..0.5.7-next.1.diff @@ -0,0 +1,1923 @@ +diff --git a/.dockerignore b/.dockerignore +index 63c9c34..05edb62 100644 +--- a/.dockerignore ++++ b/.dockerignore +@@ -1,5 +1,8 @@ + .git ++.yarn/cache ++.yarn/install-state.gz + node_modules +-packages +-!packages/backend/dist ++packages/*/src ++packages/*/node_modules + plugins ++*.local.yaml +diff --git a/.eslintignore b/.eslintignore +new file mode 100644 +index 0000000..e5b1994 +--- /dev/null ++++ b/.eslintignore +@@ -0,0 +1 @@ ++playwright.config.ts +diff --git a/.gitignore b/.gitignore +index d16a8d3..fbf8139 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -17,2 +17,11 @@ node_modules/ + ++# Yarn 3 files ++.pnp.* ++.yarn/* ++!.yarn/patches ++!.yarn/plugins ++!.yarn/releases ++!.yarn/sdks ++!.yarn/versions ++ + # Node version directives +@@ -39 +48,7 @@ site + *-credentials.yaml ++ ++# vscode database functionality support files ++*.session.sql ++ ++# E2E test reports ++e2e-test-report/ +diff --git a/app-config.production.yaml b/app-config.production.yaml +index 92f4574..8f0751c 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -1,8 +1,35 @@ + app: +- # Should be the same as backend.baseUrl when using the `app-backend` plugin +- baseUrl: http://localhost:7000 ++ # Should be the same as backend.baseUrl when using the `app-backend` plugin. ++ baseUrl: http://localhost:7007 + + backend: +- baseUrl: http://localhost:7000 +- listen: +- port: 7000 ++ # Note that the baseUrl should be the URL that the browser and other clients ++ # should use when communicating with the backend, i.e. it needs to be ++ # reachable not just from within the backend host, but from all of your ++ # callers. When its value is "http://localhost:7007", it's strictly private ++ # and can't be reached by others. ++ baseUrl: http://localhost:7007 ++ # The listener can also be expressed as a single : string. In this case we bind to ++ # all interfaces, the most permissive setting. The right value depends on your specific deployment. ++ listen: ':7007' ++ ++ # config options: https://node-postgres.com/api/client ++ database: ++ client: pg ++ connection: ++ host: ${POSTGRES_HOST} ++ port: ${POSTGRES_PORT} ++ user: ${POSTGRES_USER} ++ password: ${POSTGRES_PASSWORD} ++ # https://node-postgres.com/features/ssl ++ # you can set the sslmode configuration option via the `PGSSLMODE` environment variable ++ # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require) ++ # ssl: ++ # ca: # if you have a CA file and want to verify it you can uncomment this section ++ # $file: /ca/server.crt ++ ++catalog: ++ # Overrides the default list locations from app-config.yaml as these contain example data. ++ # See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details ++ # on how to get entities into the catalog. ++ locations: [] +diff --git a/app-config.yaml b/app-config.yaml +index 93b0c3f..08d2abf 100644 +--- a/app-config.yaml ++++ b/app-config.yaml +@@ -8,5 +8,13 @@ organization: + backend: +- baseUrl: http://localhost:7000 ++ # Used for enabling authentication, secret is shared by all backend plugins ++ # See https://backstage.io/docs/auth/service-to-service-auth for ++ # information on the format ++ # auth: ++ # keys: ++ # - secret: ${BACKEND_SECRET} ++ baseUrl: http://localhost:7007 + listen: +- port: 7000 ++ port: 7007 ++ # Uncomment the following host directive to bind to specific interfaces ++ # host: 127.0.0.1 + csp: +@@ -17,9 +25,9 @@ backend: + origin: http://localhost:3000 +- methods: [GET, POST, PUT, DELETE] ++ methods: [GET, HEAD, PATCH, POST, PUT, DELETE] + credentials: true ++ # This is for local development only, it is not recommended to use this in production ++ # The production database configuration is stored in app-config.production.yaml + database: +- client: sqlite3 ++ client: better-sqlite3 + connection: ':memory:' +- cache: +- store: memory + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir +@@ -29,2 +37,4 @@ integrations: + - host: github.com ++ # This is a Personal Access Token or PAT from GitHub. You can find out how to generate this token, and more information ++ # about setting up the GitHub integration here: https://backstage.io/docs/getting-started/configuration#setting-up-a-github-integration + token: ${GITHUB_TOKEN} +@@ -36,5 +46,8 @@ integrations: + proxy: +- '/test': +- target: 'https://example.com' +- changeOrigin: true ++ ### Example for how to add a proxy endpoint for the frontend. ++ ### A typical reason to do this is to handle HTTPS and CORS for internal services. ++ # endpoints: ++ # '/test': ++ # target: 'https://example.com' ++ # changeOrigin: true + +@@ -56,46 +69,35 @@ auth: + scaffolder: +- github: +- token: ${GITHUB_TOKEN} +- visibility: public # or 'internal' or 'private' ++ # see https://backstage.io/docs/features/software-templates/configuration for software template options + + catalog: ++ import: ++ entityFilename: catalog-info.yaml ++ pullRequestBranchName: backstage-integration + rules: +- - allow: [Component, System, API, Group, User, Resource, Location] ++ - allow: [Component, System, API, Resource, Location] + locations: +- # Backstage example components +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-components.yaml +- +- # Backstage example systems +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-systems.yaml +- +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml +- +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml ++ # Local example data, file locations are relative to the backend process, typically `packages/backend` ++ - type: file ++ target: ../../examples/entities.yaml + +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.yaml +- +- # Backstage example templates +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/react-ssr-template/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/springboot-grpc-template/template.yaml ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml + rules: + - allow: [Template] +- - type: url +- target: https://github.com/spotify/cookiecutter-golang/blob/master/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/docs-template/template.yaml ++ ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml + rules: +- - allow: [Template] ++ - allow: [User, Group] ++ ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml ++ ++ ## Uncomment these lines to add an example org ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme-corp.yaml ++ # rules: ++ # - allow: [User, Group] +diff --git a/backstage.json b/backstage.json +new file mode 100644 +index 0000000..707af29 +--- /dev/null ++++ b/backstage.json +@@ -0,0 +1,3 @@ ++{ ++ "version": "1.20.0-next.1" ++} +diff --git a/examples/entities.yaml b/examples/entities.yaml +new file mode 100644 +index 0000000..447e8b1 +--- /dev/null ++++ b/examples/entities.yaml +@@ -0,0 +1,41 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system ++apiVersion: backstage.io/v1alpha1 ++kind: System ++metadata: ++ name: examples ++spec: ++ owner: guests ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: example-website ++spec: ++ type: website ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ providesApis: [example-grpc-api] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api ++apiVersion: backstage.io/v1alpha1 ++kind: API ++metadata: ++ name: example-grpc-api ++spec: ++ type: grpc ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ definition: | ++ syntax = "proto3"; ++ ++ service Exampler { ++ rpc Example (ExampleMessage) returns (ExampleMessage) {}; ++ } ++ ++ message ExampleMessage { ++ string example = 1; ++ }; +diff --git a/examples/org.yaml b/examples/org.yaml +new file mode 100644 +index 0000000..a10e81f +--- /dev/null ++++ b/examples/org.yaml +@@ -0,0 +1,17 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user ++apiVersion: backstage.io/v1alpha1 ++kind: User ++metadata: ++ name: guest ++spec: ++ memberOf: [guests] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group ++apiVersion: backstage.io/v1alpha1 ++kind: Group ++metadata: ++ name: guests ++spec: ++ type: team ++ children: [] +diff --git a/examples/template/content/catalog-info.yaml b/examples/template/content/catalog-info.yaml +new file mode 100644 +index 0000000..d4ccca4 +--- /dev/null ++++ b/examples/template/content/catalog-info.yaml +@@ -0,0 +1,8 @@ ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: ${{ values.name | dump }} ++spec: ++ type: service ++ owner: user:guest ++ lifecycle: experimental +diff --git a/examples/template/content/index.js b/examples/template/content/index.js +new file mode 100644 +index 0000000..071ce5a +--- /dev/null ++++ b/examples/template/content/index.js +@@ -0,0 +1 @@ ++console.log('Hello from ${{ values.name }}!'); +diff --git a/examples/template/content/package.json b/examples/template/content/package.json +new file mode 100644 +index 0000000..86f968a +--- /dev/null ++++ b/examples/template/content/package.json +@@ -0,0 +1,5 @@ ++{ ++ "name": "${{ values.name }}", ++ "private": true, ++ "dependencies": {} ++} +diff --git a/examples/template/template.yaml b/examples/template/template.yaml +new file mode 100644 +index 0000000..33f262b +--- /dev/null ++++ b/examples/template/template.yaml +@@ -0,0 +1,74 @@ ++apiVersion: scaffolder.backstage.io/v1beta3 ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template ++kind: Template ++metadata: ++ name: example-nodejs-template ++ title: Example Node.js Template ++ description: An example template for the scaffolder that creates a simple Node.js service ++spec: ++ owner: user:guest ++ type: service ++ ++ # These parameters are used to generate the input form in the frontend, and are ++ # used to gather input data for the execution of the template. ++ parameters: ++ - title: Fill in some steps ++ required: ++ - name ++ properties: ++ name: ++ title: Name ++ type: string ++ description: Unique name of the component ++ ui:autofocus: true ++ ui:options: ++ rows: 5 ++ - title: Choose a location ++ required: ++ - repoUrl ++ properties: ++ repoUrl: ++ title: Repository Location ++ type: string ++ ui:field: RepoUrlPicker ++ ui:options: ++ allowedHosts: ++ - github.com ++ ++ # These steps are executed in the scaffolder backend, using data that we gathered ++ # via the parameters above. ++ steps: ++ # Each step executes an action, in this case one templates files into the working directory. ++ - id: fetch-base ++ name: Fetch Base ++ action: fetch:template ++ input: ++ url: ./content ++ values: ++ name: ${{ parameters.name }} ++ ++ # This step publishes the contents of the working directory to GitHub. ++ - id: publish ++ name: Publish ++ action: publish:github ++ input: ++ allowedHosts: ['github.com'] ++ description: This is ${{ parameters.name }} ++ repoUrl: ${{ parameters.repoUrl }} ++ ++ # The final step is to register our new component in the catalog. ++ - id: register ++ name: Register ++ action: catalog:register ++ input: ++ repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} ++ catalogInfoPath: '/catalog-info.yaml' ++ ++ # Outputs are displayed to the user after a successful execution of the template. ++ output: ++ links: ++ - title: Repository ++ url: ${{ steps['publish'].output.remoteUrl }} ++ - title: Open in catalog ++ icon: catalog ++ entityRef: ${{ steps['register'].output.entityRef }} +diff --git a/lerna.json b/lerna.json +index 322929d..529a62f 100644 +--- a/lerna.json ++++ b/lerna.json +@@ -3,4 +3,4 @@ + "npmClient": "yarn", +- "useWorkspaces": true, +- "version": "0.1.0" ++ "version": "0.1.0", ++ "$schema": "node_modules/lerna/schemas/lerna-schema.json" + } +diff --git a/package.json b/package.json +index 02902cb..2364da8 100644 +--- a/package.json ++++ b/package.json +@@ -5,3 +5,3 @@ + "engines": { +- "node": "14 || 16" ++ "node": "18 || 20" + }, +@@ -11,3 +11,4 @@ + "start-backend": "yarn workspace backend start", +- "build": "lerna run build", ++ "build:backend": "yarn workspace backend build", ++ "build:all": "backstage-cli repo build --all", + "build-image": "yarn workspace backend build-image", +@@ -15,15 +16,11 @@ + "tsc:full": "tsc --skipLibCheck false --incremental false", +- "clean": "backstage-cli clean && lerna run clean", +- "diff": "lerna run diff --", +- "test": "backstage-cli test", +- "test:all": "lerna run test -- --coverage", +- "lint": "lerna run lint --since origin/master --", +- "lint:all": "lerna run lint --", ++ "clean": "backstage-cli repo clean", ++ "test": "backstage-cli repo test", ++ "test:all": "backstage-cli repo test --coverage", ++ "test:e2e": "playwright test", ++ "fix": "backstage-cli repo fix", ++ "lint": "backstage-cli repo lint --since origin/master", ++ "lint:all": "backstage-cli repo lint", + "prettier:check": "prettier --check .", +- "create-plugin": "backstage-cli create-plugin --scope internal --no-private", +- "remove-plugin": "backstage-cli remove-plugin" +- }, +- "resolutions": { +- "graphql-language-service-interface": "2.8.2", +- "graphql-language-service-parser": "1.9.0" ++ "new": "backstage-cli new --scope internal" + }, +@@ -36,7 +33,15 @@ + "devDependencies": { +- "@backstage/cli": "^0.8.0", +- "@spotify/prettier-config": "^11.0.0", +- "concurrently": "^6.0.0", +- "lerna": "^4.0.0", +- "prettier": "^2.3.2" ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/e2e-test-utils": "^0.1.0", ++ "@playwright/test": "^1.32.3", ++ "@spotify/prettier-config": "^12.0.0", ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^9.0.0", ++ "prettier": "^2.3.2", ++ "typescript": "~5.2.0" ++ }, ++ "resolutions": { ++ "@types/react": "^17", ++ "@types/react-dom": "^17" + }, +@@ -44,3 +49,3 @@ + "lint-staged": { +- "*.{js,jsx,ts,tsx}": [ ++ "*.{js,jsx,ts,tsx,mjs,cjs}": [ + "eslint --fix", +diff --git a/packages/README.md b/packages/README.md +new file mode 100644 +index 0000000..6327fa0 +--- /dev/null ++++ b/packages/README.md +@@ -0,0 +1,9 @@ ++# The Packages Folder ++ ++This is where your own applications and centrally managed libraries live, each ++in a separate folder of its own. ++ ++From the start there's an `app` folder (for the frontend) and a `backend` folder ++(for the Node backend), but you can also add more modules in here that house ++your core additions and adaptations, such as themes, common React component ++libraries, utilities, and similar. +diff --git a/packages/app/.eslintignore b/packages/app/.eslintignore +new file mode 100644 +index 0000000..a48cf0d +--- /dev/null ++++ b/packages/app/.eslintignore +@@ -0,0 +1 @@ ++public +diff --git a/packages/app/.eslintrc.js b/packages/app/.eslintrc.js +index 13573ef..e2a53a6 100644 +--- a/packages/app/.eslintrc.js ++++ b/packages/app/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/app/cypress.json b/packages/app/cypress.json +deleted file mode 100644 +index 5de7ebf..0000000 +--- a/packages/app/cypress.json ++++ /dev/null +@@ -1,5 +0,0 @@ +-{ +- "baseUrl": "http://localhost:3001", +- "fixturesFolder": false, +- "pluginsFile": false +-} +diff --git a/packages/app/cypress/.eslintrc.json b/packages/app/cypress/.eslintrc.json +deleted file mode 100644 +index 2b3a458..0000000 +--- a/packages/app/cypress/.eslintrc.json ++++ /dev/null +@@ -1,21 +0,0 @@ +-{ +- "plugins": ["cypress"], +- "extends": ["plugin:cypress/recommended"], +- "rules": { +- "jest/expect-expect": [ +- "error", +- { +- "assertFunctionNames": ["expect", "cy.contains"] +- } +- ], +- "import/no-extraneous-dependencies": [ +- "error", +- { +- "devDependencies": true, +- "optionalDependencies": true, +- "peerDependencies": true, +- "bundledDependencies": true +- } +- ] +- } +-} +diff --git a/packages/app/cypress/integration/app.js b/packages/app/cypress/integration/app.js +deleted file mode 100644 +index 43fb2e3..0000000 +--- a/packages/app/cypress/integration/app.js ++++ /dev/null +@@ -1,6 +0,0 @@ +-describe('App', () => { +- it('should render the catalog', () => { +- cy.visit('/'); +- cy.contains('My Company Catalog'); +- }); +-}); +diff --git a/packages/app/e2e-tests/app.test.ts b/packages/app/e2e-tests/app.test.ts +new file mode 100644 +index 0000000..d45bc0d +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,23 @@ ++/* ++ * Copyright 2020 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { test, expect } from '@playwright/test'; ++ ++test('App should render the welcome page', async ({ page }) => { ++ await page.goto('/'); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 8bdb8f7..2100405 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,22 +5,38 @@ + "bundled": true, ++ "backstage": { ++ "role": "frontend" ++ }, ++ "scripts": { ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "clean": "backstage-cli package clean", ++ "test": "backstage-cli package test", ++ "lint": "backstage-cli package lint" ++ }, + "dependencies": { +- "@backstage/catalog-model": "^0.9.5", +- "@backstage/cli": "^0.8.0", +- "@backstage/core-app-api": "^0.1.18", +- "@backstage/core-components": "^0.7.1", +- "@backstage/core-plugin-api": "^0.1.11", +- "@backstage/integration-react": "^0.1.12", +- "@backstage/plugin-api-docs": "^0.6.12", +- "@backstage/plugin-catalog": "^0.7.2", +- "@backstage/plugin-catalog-import": "^0.7.3", +- "@backstage/plugin-catalog-react": "^0.6.1", +- "@backstage/plugin-github-actions": "^0.4.22", +- "@backstage/plugin-org": "^0.3.27", +- "@backstage/plugin-scaffolder": "^0.11.8", +- "@backstage/plugin-search": "^0.4.15", +- "@backstage/plugin-tech-radar": "^0.4.11", +- "@backstage/plugin-techdocs": "^0.12.3", +- "@backstage/plugin-user-settings": "^0.3.10", +- "@backstage/test-utils": "^0.1.19", +- "@backstage/theme": "^0.2.11", ++ "@backstage/app-defaults": "^1.4.5-next.1", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/core-app-api": "^1.11.1-next.0", ++ "@backstage/core-components": "^0.13.8-next.1", ++ "@backstage/core-plugin-api": "^1.8.0-next.0", ++ "@backstage/integration-react": "^1.1.21-next.1", ++ "@backstage/plugin-api-docs": "^0.9.13-next.1", ++ "@backstage/plugin-catalog": "^1.15.0-next.1", ++ "@backstage/plugin-catalog-common": "^1.0.17", ++ "@backstage/plugin-catalog-graph": "^0.2.38-next.1", ++ "@backstage/plugin-catalog-import": "^0.10.2-next.1", ++ "@backstage/plugin-catalog-react": "^1.9.0-next.1", ++ "@backstage/plugin-github-actions": "^0.6.7-next.1", ++ "@backstage/plugin-org": "^0.6.16-next.1", ++ "@backstage/plugin-permission-react": "^0.4.17-next.0", ++ "@backstage/plugin-scaffolder": "^1.16.0-next.1", ++ "@backstage/plugin-search": "^1.4.2-next.1", ++ "@backstage/plugin-search-react": "^1.7.2-next.1", ++ "@backstage/plugin-tech-radar": "^0.6.10-next.1", ++ "@backstage/plugin-techdocs": "^1.8.1-next.1", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.2-next.1", ++ "@backstage/plugin-techdocs-react": "^1.1.13-next.1", ++ "@backstage/plugin-user-settings": "^0.7.12-next.1", ++ "@backstage/theme": "^0.4.4-next.0", + "@material-ui/core": "^4.12.2", +@@ -28,30 +44,17 @@ + "history": "^5.0.0", +- "react": "^16.13.1", +- "react-dom": "^16.13.1", +- "react-router": "6.0.0-beta.0", +- "react-router-dom": "6.0.0-beta.0", +- "react-use": "^15.3.3" ++ "react": "^17.0.2", ++ "react-dom": "^17.0.2", ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { ++ "@backstage/test-utils": "^1.4.5-next.0", ++ "@playwright/test": "^1.32.3", + "@testing-library/jest-dom": "^5.10.1", +- "@testing-library/react": "^10.4.1", +- "@testing-library/user-event": "^12.0.7", +- "@types/jest": "^26.0.7", +- "@types/node": "^14.14.32", ++ "@testing-library/react": "^12.1.3", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^8.0.0", + "@types/react-dom": "*", +- "cross-env": "^7.0.0", +- "cypress": "^7.3.0", +- "eslint-plugin-cypress": "^2.10.3", +- "start-server-and-test": "^1.10.11" +- }, +- "scripts": { +- "start": "backstage-cli app:serve", +- "build": "backstage-cli app:build", +- "test": "backstage-cli test", +- "lint": "backstage-cli lint", +- "clean": "backstage-cli clean", +- "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", +- "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", +- "cy:dev": "cypress open", +- "cy:run": "cypress run" ++ "cross-env": "^7.0.0" + }, +diff --git a/packages/app/public/index.html b/packages/app/public/index.html +index b93896c..fc758ee 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -10,3 +10,2 @@ + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index 82bc479..b94cac7 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -12,5 +12,5 @@ describe('App', () => { + app: { title: 'Test' }, +- backend: { baseUrl: 'http://localhost:7000' }, ++ backend: { baseUrl: 'http://localhost:7007' }, + techdocs: { +- storageUrl: 'http://localhost:7000/api/techdocs/static/docs', ++ storageUrl: 'http://localhost:7007/api/techdocs/static/docs', + }, +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 4cd8368..8d62f29 100644 +--- a/packages/app/src/App.tsx ++++ b/packages/app/src/App.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { Navigate, Route } from 'react-router'; ++import { Navigate, Route } from 'react-router-dom'; + import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; +@@ -13,2 +13,3 @@ import { + import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; ++import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +@@ -16,3 +17,2 @@ import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -21,2 +21,4 @@ import { + } from '@backstage/plugin-techdocs'; ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; + import { UserSettingsPage } from '@backstage/plugin-user-settings'; +@@ -28,3 +30,7 @@ import { Root } from './components/Root'; + import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; +-import { createApp, FlatRoutes } from '@backstage/core-app-api'; ++import { createApp } from '@backstage/app-defaults'; ++import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; ++import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; ++import { RequirePermission } from '@backstage/plugin-permission-react'; ++import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; + +@@ -36,5 +42,6 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); + bind(apiDocsPlugin.externalRoutes, { +- createComponent: scaffolderPlugin.routes.root, ++ registerApi: catalogImportPlugin.routes.importPage, + }); +@@ -42,2 +49,6 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, ++ }); ++ bind(orgPlugin.externalRoutes, { ++ catalogIndex: catalogPlugin.routes.catalogIndex, + }); +@@ -46,8 +57,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -59,5 +67,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -72,3 +82,10 @@ const routes = ( + /> +- } /> ++ ++ ++ ++ } ++ /> + }> +@@ -77,2 +94,3 @@ const routes = ( + } /> ++ } /> + +@@ -80,4 +98,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -87,5 +105,3 @@ const App = () => ( + +- ++ , + ); +- +-export default App; +diff --git a/packages/app/src/components/Root/LogoFull.tsx b/packages/app/src/components/Root/LogoFull.tsx +index c7b1c84..47e3b73 100644 +--- a/packages/app/src/components/Root/LogoFull.tsx ++++ b/packages/app/src/components/Root/LogoFull.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/LogoIcon.tsx b/packages/app/src/components/Root/LogoIcon.tsx +index 073cf6e..7eae8c7 100644 +--- a/packages/app/src/components/Root/LogoIcon.tsx ++++ b/packages/app/src/components/Root/LogoIcon.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx +index 198e7ec..6768b48 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,19 +1,3 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- +-import React, { useContext, PropsWithChildren } from 'react'; +-import { Link, makeStyles } from '@material-ui/core'; ++import React, { PropsWithChildren } from 'react'; ++import { makeStyles } from '@material-ui/core'; + import HomeIcon from '@material-ui/icons/Home'; +@@ -25,15 +9,21 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; +-import { Settings as SidebarSettings } from '@backstage/plugin-user-settings'; +-import { SidebarSearch } from '@backstage/plugin-search'; ++import { ++ Settings as SidebarSettings, ++ UserSettingsSignInAvatar, ++} from '@backstage/plugin-user-settings'; ++import { SidebarSearchModal } from '@backstage/plugin-search'; + import { + Sidebar, +- SidebarPage, + sidebarConfig, +- SidebarContext, +- SidebarItem, + SidebarDivider, +- SidebarSpace, ++ SidebarGroup, ++ SidebarItem, ++ SidebarPage, + SidebarScrollWrapper, ++ SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; ++import MenuIcon from '@material-ui/icons/Menu'; ++import SearchIcon from '@material-ui/icons/Search'; + +@@ -56,3 +46,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -60,8 +50,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -76,17 +61,27 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ } to="/search"> ++ ++ + +- {/* Global nav, not org-specific */} +- +- +- +- +- {/* End global nav */} +- +- +- +- ++ }> ++ {/* Global nav, not org-specific */} ++ ++ ++ ++ ++ {/* End global nav */} ++ ++ ++ ++ ++ + + +- ++ } ++ to="/settings" ++ > ++ ++ + +diff --git a/packages/app/src/components/Root/index.ts b/packages/app/src/components/Root/index.ts +index dff706f..3528881 100644 +--- a/packages/app/src/components/Root/index.ts ++++ b/packages/app/src/components/Root/index.ts +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + export { Root } from './Root'; +diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx +index d3b4b78..6722ea2 100644 +--- a/packages/app/src/components/catalog/EntityPage.tsx ++++ b/packages/app/src/components/catalog/EntityPage.tsx +@@ -1,16 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ + import React from 'react'; +@@ -29,3 +14,2 @@ import { + EntityDependsOnResourcesCard, +- EntitySystemDiagramCard, + EntityHasComponentsCard, +@@ -43,2 +27,4 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +@@ -56,2 +42,27 @@ import { EntityTechdocsContent } from '@backstage/plugin-techdocs'; + import { EmptyState } from '@backstage/core-components'; ++import { ++ Direction, ++ EntityCatalogGraphCard, ++} from '@backstage/plugin-catalog-graph'; ++import { ++ RELATION_API_CONSUMED_BY, ++ RELATION_API_PROVIDED_BY, ++ RELATION_CONSUMES_API, ++ RELATION_DEPENDENCY_OF, ++ RELATION_DEPENDS_ON, ++ RELATION_HAS_PART, ++ RELATION_PART_OF, ++ RELATION_PROVIDES_API, ++} from '@backstage/catalog-model'; ++ ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); + +@@ -94,2 +105,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -110,2 +129,6 @@ const overviewContent = ( + ++ ++ ++ ++ + +@@ -152,3 +175,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -179,3 +202,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -198,3 +221,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -225,2 +248,5 @@ const apiPage = ( + ++ ++ ++ + +@@ -292,3 +318,9 @@ const systemPage = ( + +- ++ ++ ++ ++ ++ ++ ++ + +@@ -304,3 +336,19 @@ const systemPage = ( + +- ++ + +@@ -317,2 +365,5 @@ const domainPage = ( + ++ ++ ++ + +diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx +index 7b3c2b2..1788dde 100644 +--- a/packages/app/src/components/search/SearchPage.tsx ++++ b/packages/app/src/components/search/SearchPage.tsx +@@ -1,5 +1,12 @@ + import React from 'react'; +-import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; ++import { makeStyles, Theme, Grid, Paper } from '@material-ui/core'; + +-import { CatalogResultListItem } from '@backstage/plugin-catalog'; ++import { CatalogSearchResultListItem } from '@backstage/plugin-catalog'; ++import { ++ catalogApiRef, ++ CATALOG_FILTER_EXISTS, ++} from '@backstage/plugin-catalog-react'; ++import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; ++ ++import { SearchType } from '@backstage/plugin-search'; + import { +@@ -8,5 +15,13 @@ import { + SearchResult, +- DefaultResultListItem, +-} from '@backstage/plugin-search'; +-import { Content, Header, Page } from '@backstage/core-components'; ++ SearchPagination, ++ useSearch, ++} from '@backstage/plugin-search-react'; ++import { ++ CatalogIcon, ++ Content, ++ DocsIcon, ++ Header, ++ Page, ++} from '@backstage/core-components'; ++import { useApi } from '@backstage/core-plugin-api'; + +@@ -18,2 +33,3 @@ const useStyles = makeStyles((theme: Theme) => ({ + padding: theme.spacing(2), ++ marginTop: theme.spacing(2), + }, +@@ -28,2 +44,4 @@ const SearchPage = () => { + const classes = useStyles(); ++ const { types } = useSearch(); ++ const catalogApi = useApi(catalogApiRef); + +@@ -36,3 +54,3 @@ const SearchPage = () => { + +- ++ + +@@ -40,5 +58,43 @@ const SearchPage = () => { + ++ , ++ }, ++ { ++ value: 'techdocs', ++ name: 'Documentation', ++ icon: , ++ }, ++ ]} ++ /> + ++ {types.includes('techdocs') && ( ++ { ++ // Return a list of entities which are documented. ++ const { items } = await catalogApi.getEntities({ ++ fields: ['metadata.name'], ++ filter: { ++ 'metadata.annotations.backstage.io/techdocs-ref': ++ CATALOG_FILTER_EXISTS, ++ }, ++ }); ++ ++ const names = items.map(entity => entity.metadata.name); ++ names.sort(); ++ return names; ++ }} ++ /> ++ )} + { + className={classes.filter} ++ label="Lifecycle" + name="lifecycle" +@@ -54,25 +111,6 @@ const SearchPage = () => { + ++ + +- {({ results }) => ( +- +- {results.map(({ type, document }) => { +- switch (type) { +- case 'software-catalog': +- return ( +- +- ); +- default: +- return ( +- +- ); +- } +- })} +- +- )} ++ } /> ++ } /> + +diff --git a/packages/backend/.eslintrc.js b/packages/backend/.eslintrc.js +index 16a033d..e2a53a6 100644 +--- a/packages/backend/.eslintrc.js ++++ b/packages/backend/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint.backend')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index 31231a3..18548e9 100644 +--- a/packages/backend/Dockerfile ++++ b/packages/backend/Dockerfile +@@ -7,3 +7,3 @@ + # yarn tsc +-# yarn build ++# yarn build:backend + # +@@ -11,6 +11,29 @@ + +-FROM node:14-buster-slim ++FROM node:18-bookworm-slim + ++# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends python3 g++ build-essential && \ ++ yarn config set python /usr/bin/python3 ++ ++# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, ++# in which case you should also move better-sqlite3 to "devDependencies" in package.json. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends libsqlite3-dev ++ ++# From here on we use the least-privileged `node` user to run the backend. ++USER node ++ ++# This should create the app dir as `node`. ++# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. ++# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. + WORKDIR /app + ++# This switches many Node.js dependencies to production mode. ++ENV NODE_ENV production ++ + # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. +@@ -18,11 +41,12 @@ WORKDIR /app + # and along with yarn.lock and the root package.json, that's enough to run yarn install. +-COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ ++COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ + RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz + +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + + # Then copy the rest of the backend bundle, along with any other files we might want. +-COPY packages/backend/dist/bundle.tar.gz app-config.yaml ./ ++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"] +diff --git a/packages/backend/README.md b/packages/backend/README.md +index 81e0f80..3607b0a 100644 +--- a/packages/backend/README.md ++++ b/packages/backend/README.md +@@ -18,4 +18,2 @@ To run the example backend, first go to the project root and run + yarn install +-yarn tsc +-yarn build + ``` +@@ -27,7 +25,2 @@ After that, go to the `packages/backend` directory and run + ```bash +-AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x \ +-AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x \ +-AUTH_OAUTH2_CLIENT_ID=x AUTH_OAUTH2_CLIENT_SECRET=x \ +-AUTH_OAUTH2_AUTH_URL=x AUTH_OAUTH2_TOKEN_URL=x \ +-LOG_LEVEL=debug \ + yarn start +@@ -35,6 +28,6 @@ yarn start + +-Substitute `x` for actual values, or leave them as dummy values just to try out +-the backend without using the auth or sentry features. ++If you want to override any configuration locally, for example adding any secrets, ++you can do so in `app-config.local.yaml`. + +-The backend starts up on port 7000 per default. ++The backend starts up on port 7007 per default. + +@@ -45,3 +38,3 @@ locations to the backend. These are places where the backend can find some + entity descriptor data to consume and serve. For more information, see +-[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog). ++[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). + +@@ -65,2 +58,2 @@ and + - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +-- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) ++- [Backstage Documentation](https://backstage.io/docs) +diff --git a/packages/backend/package.json b/packages/backend/package.json +index bdbb85f..f224eaf 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,32 +6,37 @@ + "private": true, ++ "backstage": { ++ "role": "backend" ++ }, + "scripts": { +- "build": "backstage-cli backend:bundle", +- "build-image": "docker build ../.. -f Dockerfile --tag backstage", +- "start": "backstage-cli backend:dev", +- "lint": "backstage-cli lint", +- "test": "backstage-cli test", +- "clean": "backstage-cli clean", +- "migrate:create": "knex migrate:make -x ts" ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "lint": "backstage-cli package lint", ++ "test": "backstage-cli package test", ++ "clean": "backstage-cli package clean", ++ "build-image": "docker build ../.. -f Dockerfile --tag backstage" + }, + "dependencies": { +- "app": "0.0.0", +- "@backstage/backend-common": "^0.9.7", +- "@backstage/catalog-model": "^0.9.5", +- "@backstage/catalog-client": "^0.5.0", +- "@backstage/config": "^0.1.10", +- "@backstage/plugin-app-backend": "^0.3.17", +- "@backstage/plugin-auth-backend": "^0.4.5", +- "@backstage/plugin-catalog-backend": "^0.17.1", +- "@backstage/plugin-proxy-backend": "^0.2.13", +- "@backstage/plugin-scaffolder-backend": "^0.15.10", +- "@backstage/plugin-search-backend": "^0.2.6", +- "@backstage/plugin-search-backend-node": "^0.4.2", +- "@backstage/plugin-techdocs-backend": "^0.10.5", +- "@gitbeaker/node": "^30.2.0", +- "@octokit/rest": "^18.5.3", +- "dockerode": "^3.2.1", ++ "@backstage/backend-common": "^0.19.9-next.1", ++ "@backstage/backend-tasks": "^0.5.12-next.1", ++ "@backstage/catalog-client": "^1.4.5", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/config": "^1.1.1", ++ "@backstage/plugin-app-backend": "^0.3.55-next.1", ++ "@backstage/plugin-auth-backend": "^0.20.0-next.1", ++ "@backstage/plugin-auth-node": "^0.4.1-next.1", ++ "@backstage/plugin-catalog-backend": "^1.15.0-next.1", ++ "@backstage/plugin-permission-common": "^0.7.9", ++ "@backstage/plugin-permission-node": "^0.7.18-next.1", ++ "@backstage/plugin-proxy-backend": "^0.4.5-next.1", ++ "@backstage/plugin-scaffolder-backend": "^1.19.0-next.1", ++ "@backstage/plugin-search-backend": "^1.4.7-next.1", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.16-next.1", ++ "@backstage/plugin-search-backend-node": "^1.2.11-next.1", ++ "@backstage/plugin-techdocs-backend": "^1.9.0-next.1", ++ "app": "link:../app", ++ "better-sqlite3": "^8.0.0", ++ "dockerode": "^3.3.1", + "express": "^4.17.1", + "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "pg": "^8.3.0", + "winston": "^3.2.1" +@@ -39,6 +44,7 @@ + "devDependencies": { +- "@backstage/cli": "^0.8.0", +- "@types/dockerode": "^3.2.1", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@types/dockerode": "^3.3.0", + "@types/express": "^4.17.6", +- "@types/express-serve-static-core": "^4.17.5" ++ "@types/express-serve-static-core": "^4.17.5", ++ "@types/luxon": "^2.0.4" + }, +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index f2b14b2..04c4ff9 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -17,5 +17,7 @@ import { + DatabaseManager, +- SingleHostDiscovery, ++ HostDiscovery, + UrlReaders, ++ ServerTokenManager, + } from '@backstage/backend-common'; ++import { TaskScheduler } from '@backstage/backend-tasks'; + import { Config } from '@backstage/config'; +@@ -29,2 +31,4 @@ import search from './plugins/search'; + import { PluginEnvironment } from './types'; ++import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; + +@@ -33,8 +37,17 @@ function makeCreateEnv(config: Config) { + const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++ const discovery = HostDiscovery.fromConfig(config); ++ const cacheManager = CacheManager.fromConfig(config); ++ const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); ++ const tokenManager = ServerTokenManager.noop(); ++ const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); + +- root.info(`Created UrlReader ${reader}`); ++ const identity = DefaultIdentityClient.create({ ++ discovery, ++ }); ++ const permissions = ServerPermissionClient.fromConfig(config, { ++ discovery, ++ tokenManager, ++ }); + +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++ root.info(`Created UrlReader ${reader}`); + +@@ -44,3 +57,15 @@ function makeCreateEnv(config: Config) { + const cache = cacheManager.forPlugin(plugin); +- return { logger, database, cache, config, reader, discovery }; ++ const scheduler = taskScheduler.forPlugin(plugin); ++ return { ++ logger, ++ database, ++ cache, ++ config, ++ reader, ++ discovery, ++ tokenManager, ++ scheduler, ++ permissions, ++ identity, ++ }; + }; +@@ -70,2 +95,4 @@ async function main() { + apiRouter.use('/search', await search(searchEnv)); ++ ++ // Add backends ABOVE this line; this 404 handler is the catch-all fallback + apiRouter.use(notFoundHandler()); +@@ -85,3 +112,3 @@ module.hot?.accept(); + main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); ++ console.error('Backend failed to start up', error); + process.exit(1); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +index 07fb04f..7c37f68 100644 +--- a/packages/backend/src/plugins/app.ts ++++ b/packages/backend/src/plugins/app.ts +@@ -4,9 +4,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + return await createRouter({ +- logger, +- config, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, + appPackageName: 'app', +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +index 5216510..77eb6aa 100644 +--- a/packages/backend/src/plugins/auth.ts ++++ b/packages/backend/src/plugins/auth.ts +@@ -1,2 +1,6 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; ++import { ++ createRouter, ++ providers, ++ defaultAuthProviderFactories, ++} from '@backstage/plugin-auth-backend'; + import { Router } from 'express'; +@@ -4,9 +8,47 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, database, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, ++ providerFactories: { ++ ...defaultAuthProviderFactories, ++ ++ // This replaces the default GitHub auth provider with a customized one. ++ // The `signIn` option enables sign-in for this provider, using the ++ // identity resolution logic that's provided in the `resolver` callback. ++ // ++ // This particular resolver makes all users share a single "guest" identity. ++ // It should only be used for testing and trying out Backstage. ++ // ++ // If you want to use a production ready resolver you can switch to ++ // the one that is commented out below, it looks up a user entity in the ++ // catalog using the GitHub username of the authenticated user. ++ // That resolver requires you to have user entities populated in the catalog, ++ // for example using https://backstage.io/docs/integrations/github/org ++ // ++ // There are other resolvers to choose from, and you can also create ++ // your own, see the auth documentation for more details: ++ // ++ // https://backstage.io/docs/auth/identity-resolver ++ github: providers.github.create({ ++ signIn: { ++ resolver(_, ctx) { ++ const userRef = 'user:default/guest'; // Must be a full entity reference ++ return ctx.issueToken({ ++ claims: { ++ sub: userRef, // The user's own identity ++ ent: [userRef], // A list of identities that the user claims ownership through ++ }, ++ }); ++ }, ++ // resolver: providers.github.resolvers.usernameMatchingUserEntityName(), ++ }, ++ }), ++ }, ++ }); + } +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +index 506f6d9..54ec393 100644 +--- a/packages/backend/src/plugins/proxy.ts ++++ b/packages/backend/src/plugins/proxy.ts +@@ -4,8 +4,10 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ }); + } +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +index 6be2e97..a12fee2 100644 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ b/packages/backend/src/plugins/scaffolder.ts +@@ -1,5 +1,3 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; + import { CatalogClient } from '@backstage/catalog-client'; + import { createRouter } from '@backstage/plugin-scaffolder-backend'; +-import Docker from 'dockerode'; + import { Router } from 'express'; +@@ -7,20 +5,17 @@ import type { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- database, +- reader, +- discovery, +-}: PluginEnvironment): Promise { +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- const catalogClient = new CatalogClient({ discoveryApi: discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ const catalogClient = new CatalogClient({ ++ discoveryApi: env.discovery, ++ }); + + return await createRouter({ +- containerRunner, +- logger, +- config, +- database, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ reader: env.reader, + catalogClient, +- reader, ++ identity: env.identity, ++ permissions: env.permissions, + }); +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +index 7fc317d..e9469dc 100644 +--- a/packages/backend/src/plugins/search.ts ++++ b/packages/backend/src/plugins/search.ts +@@ -7,18 +7,44 @@ import { + import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; ++import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend'; ++import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; ++import { Router } from 'express'; + +-export default async function createPlugin({ +- logger, +- discovery, +- config, +-}: PluginEnvironment) { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); ++ const searchEngine = new LunrSearchEngine({ ++ logger: env.logger, ++ }); ++ const indexBuilder = new IndexBuilder({ ++ logger: env.logger, ++ searchEngine, ++ }); ++ ++ const schedule = env.scheduler.createScheduledTaskRunner({ ++ frequency: { minutes: 10 }, ++ timeout: { minutes: 15 }, ++ // A 3 second delay gives the backend server a chance to initialize before ++ // any collators are executed, which may attempt requests against the API. ++ initialDelay: { seconds: 3 }, ++ }); + + // Collators are responsible for gathering documents known to plugins. This +- // particular collator gathers entities from the software catalog. ++ // collator gathers entities from the software catalog. ++ indexBuilder.addCollator({ ++ schedule, ++ factory: DefaultCatalogCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, ++ }), ++ }); ++ ++ // collator gathers entities from techdocs. + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { discovery }), ++ schedule, ++ factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ logger: env.logger, ++ tokenManager: env.tokenManager, ++ }), + }); +@@ -28,6 +54,4 @@ export default async function createPlugin({ + const { scheduler } = await indexBuilder.build(); ++ scheduler.start(); + +- // A 3 second delay gives the backend server a chance to initialize before +- // any collators are executed, which may attempt requests against the API. +- setTimeout(() => scheduler.start(), 3000); + useHotCleanup(module, () => scheduler.stop()); +@@ -36,3 +60,6 @@ export default async function createPlugin({ + engine: indexBuilder.getSearchEngine(), +- logger, ++ types: indexBuilder.getDocumentTypes(), ++ permissions: env.permissions, ++ config: env.config, ++ logger: env.logger, + }); +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +index 906d86d..be8bb0c 100644 +--- a/packages/backend/src/plugins/techdocs.ts ++++ b/packages/backend/src/plugins/techdocs.ts +@@ -11,12 +11,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, ++ const preparers = await Preparers.fromConfig(env.config, { ++ logger: env.logger, ++ reader: env.reader, + }); +@@ -28,4 +25,4 @@ export default async function createPlugin({ + // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, ++ const generators = await Generators.fromConfig(env.config, { ++ logger: env.logger, + containerRunner, +@@ -36,5 +33,5 @@ export default async function createPlugin({ + // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, ++ const publisher = await Publisher.fromConfig(env.config, { ++ logger: env.logger, ++ discovery: env.discovery, + }); +@@ -48,5 +45,6 @@ export default async function createPlugin({ + publisher, +- logger, +- config, +- discovery, ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ cache: env.cache, + }); +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +index 6c78a2a..9cd2c74 100644 +--- a/packages/backend/src/types.ts ++++ b/packages/backend/src/types.ts +@@ -6,4 +6,8 @@ import { + PluginEndpointDiscovery, ++ TokenManager, + UrlReader, + } from '@backstage/backend-common'; ++import { PluginTaskScheduler } from '@backstage/backend-tasks'; ++import { PermissionEvaluator } from '@backstage/plugin-permission-common'; ++import { IdentityApi } from '@backstage/plugin-auth-node'; + +@@ -16,2 +20,6 @@ export type PluginEnvironment = { + discovery: PluginEndpointDiscovery; ++ tokenManager: TokenManager; ++ scheduler: PluginTaskScheduler; ++ permissions: PermissionEvaluator; ++ identity: IdentityApi; + }; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..37c7fb1 +--- /dev/null ++++ b/playwright.config.ts +@@ -0,0 +1,60 @@ ++/* ++ * Copyright 2023 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { defineConfig } from '@playwright/test'; ++import { generateProjects } from '@backstage/e2e-test-utils/playwright'; ++ ++/** ++ * See https://playwright.dev/docs/test-configuration. ++ */ ++export default defineConfig({ ++ timeout: 60_000, ++ ++ expect: { ++ timeout: 5_000, ++ }, ++ ++ // Run your local dev server before starting the tests ++ webServer: process.env.CI ++ ? [] ++ : [ ++ { ++ command: 'yarn start', ++ port: 3000, ++ reuseExistingServer: true, ++ timeout: 60_000, ++ }, ++ ], ++ ++ forbidOnly: !!process.env.CI, ++ ++ retries: process.env.CI ? 2 : 0, ++ ++ reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]], ++ ++ use: { ++ actionTimeout: 0, ++ baseURL: ++ process.env.PLAYWRIGHT_URL ?? ++ (process.env.CI ? 'http://localhost:7007' : 'http://localhost:3000'), ++ screenshot: 'only-on-failure', ++ trace: 'on-first-retry', ++ }, ++ ++ outputDir: 'node_modules/.cache/e2e-test-results', ++ ++ projects: generateProjects(), // Find all packages with e2e-test folders ++}); +diff --git a/plugins/README.md b/plugins/README.md +new file mode 100644 +index 0000000..d7865fd +--- /dev/null ++++ b/plugins/README.md +@@ -0,0 +1,9 @@ ++# The Plugins Folder ++ ++This is where your own plugins and their associated modules live, each in a ++separate folder of its own. ++ ++If you want to create a new plugin here, go to your project root directory, run ++the command `yarn new`, and follow the on-screen instructions. ++ ++You can also check out existing plugins on [the plugin marketplace](https://backstage.io/plugins)! +diff --git a/yarn.lock b/yarn.lock +new file mode 100644 +index 0000000..5ad7fe2 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1 @@ ++# intentionally left empty diff --git a/diffs/0.4.1..0.5.7-next.1.diff b/diffs/0.4.1..0.5.7-next.1.diff new file mode 100644 index 00000000..2bd1efcd --- /dev/null +++ b/diffs/0.4.1..0.5.7-next.1.diff @@ -0,0 +1,1923 @@ +diff --git a/.dockerignore b/.dockerignore +index 63c9c34..05edb62 100644 +--- a/.dockerignore ++++ b/.dockerignore +@@ -1,5 +1,8 @@ + .git ++.yarn/cache ++.yarn/install-state.gz + node_modules +-packages +-!packages/backend/dist ++packages/*/src ++packages/*/node_modules + plugins ++*.local.yaml +diff --git a/.eslintignore b/.eslintignore +new file mode 100644 +index 0000000..e5b1994 +--- /dev/null ++++ b/.eslintignore +@@ -0,0 +1 @@ ++playwright.config.ts +diff --git a/.gitignore b/.gitignore +index d16a8d3..fbf8139 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -17,2 +17,11 @@ node_modules/ + ++# Yarn 3 files ++.pnp.* ++.yarn/* ++!.yarn/patches ++!.yarn/plugins ++!.yarn/releases ++!.yarn/sdks ++!.yarn/versions ++ + # Node version directives +@@ -39 +48,7 @@ site + *-credentials.yaml ++ ++# vscode database functionality support files ++*.session.sql ++ ++# E2E test reports ++e2e-test-report/ +diff --git a/app-config.production.yaml b/app-config.production.yaml +index 92f4574..8f0751c 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -1,8 +1,35 @@ + app: +- # Should be the same as backend.baseUrl when using the `app-backend` plugin +- baseUrl: http://localhost:7000 ++ # Should be the same as backend.baseUrl when using the `app-backend` plugin. ++ baseUrl: http://localhost:7007 + + backend: +- baseUrl: http://localhost:7000 +- listen: +- port: 7000 ++ # Note that the baseUrl should be the URL that the browser and other clients ++ # should use when communicating with the backend, i.e. it needs to be ++ # reachable not just from within the backend host, but from all of your ++ # callers. When its value is "http://localhost:7007", it's strictly private ++ # and can't be reached by others. ++ baseUrl: http://localhost:7007 ++ # The listener can also be expressed as a single : string. In this case we bind to ++ # all interfaces, the most permissive setting. The right value depends on your specific deployment. ++ listen: ':7007' ++ ++ # config options: https://node-postgres.com/api/client ++ database: ++ client: pg ++ connection: ++ host: ${POSTGRES_HOST} ++ port: ${POSTGRES_PORT} ++ user: ${POSTGRES_USER} ++ password: ${POSTGRES_PASSWORD} ++ # https://node-postgres.com/features/ssl ++ # you can set the sslmode configuration option via the `PGSSLMODE` environment variable ++ # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require) ++ # ssl: ++ # ca: # if you have a CA file and want to verify it you can uncomment this section ++ # $file: /ca/server.crt ++ ++catalog: ++ # Overrides the default list locations from app-config.yaml as these contain example data. ++ # See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details ++ # on how to get entities into the catalog. ++ locations: [] +diff --git a/app-config.yaml b/app-config.yaml +index 93b0c3f..08d2abf 100644 +--- a/app-config.yaml ++++ b/app-config.yaml +@@ -8,5 +8,13 @@ organization: + backend: +- baseUrl: http://localhost:7000 ++ # Used for enabling authentication, secret is shared by all backend plugins ++ # See https://backstage.io/docs/auth/service-to-service-auth for ++ # information on the format ++ # auth: ++ # keys: ++ # - secret: ${BACKEND_SECRET} ++ baseUrl: http://localhost:7007 + listen: +- port: 7000 ++ port: 7007 ++ # Uncomment the following host directive to bind to specific interfaces ++ # host: 127.0.0.1 + csp: +@@ -17,9 +25,9 @@ backend: + origin: http://localhost:3000 +- methods: [GET, POST, PUT, DELETE] ++ methods: [GET, HEAD, PATCH, POST, PUT, DELETE] + credentials: true ++ # This is for local development only, it is not recommended to use this in production ++ # The production database configuration is stored in app-config.production.yaml + database: +- client: sqlite3 ++ client: better-sqlite3 + connection: ':memory:' +- cache: +- store: memory + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir +@@ -29,2 +37,4 @@ integrations: + - host: github.com ++ # This is a Personal Access Token or PAT from GitHub. You can find out how to generate this token, and more information ++ # about setting up the GitHub integration here: https://backstage.io/docs/getting-started/configuration#setting-up-a-github-integration + token: ${GITHUB_TOKEN} +@@ -36,5 +46,8 @@ integrations: + proxy: +- '/test': +- target: 'https://example.com' +- changeOrigin: true ++ ### Example for how to add a proxy endpoint for the frontend. ++ ### A typical reason to do this is to handle HTTPS and CORS for internal services. ++ # endpoints: ++ # '/test': ++ # target: 'https://example.com' ++ # changeOrigin: true + +@@ -56,46 +69,35 @@ auth: + scaffolder: +- github: +- token: ${GITHUB_TOKEN} +- visibility: public # or 'internal' or 'private' ++ # see https://backstage.io/docs/features/software-templates/configuration for software template options + + catalog: ++ import: ++ entityFilename: catalog-info.yaml ++ pullRequestBranchName: backstage-integration + rules: +- - allow: [Component, System, API, Group, User, Resource, Location] ++ - allow: [Component, System, API, Resource, Location] + locations: +- # Backstage example components +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-components.yaml +- +- # Backstage example systems +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-systems.yaml +- +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml +- +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml ++ # Local example data, file locations are relative to the backend process, typically `packages/backend` ++ - type: file ++ target: ../../examples/entities.yaml + +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.yaml +- +- # Backstage example templates +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/react-ssr-template/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/springboot-grpc-template/template.yaml ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml + rules: + - allow: [Template] +- - type: url +- target: https://github.com/spotify/cookiecutter-golang/blob/master/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/docs-template/template.yaml ++ ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml + rules: +- - allow: [Template] ++ - allow: [User, Group] ++ ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml ++ ++ ## Uncomment these lines to add an example org ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme-corp.yaml ++ # rules: ++ # - allow: [User, Group] +diff --git a/backstage.json b/backstage.json +new file mode 100644 +index 0000000..707af29 +--- /dev/null ++++ b/backstage.json +@@ -0,0 +1,3 @@ ++{ ++ "version": "1.20.0-next.1" ++} +diff --git a/examples/entities.yaml b/examples/entities.yaml +new file mode 100644 +index 0000000..447e8b1 +--- /dev/null ++++ b/examples/entities.yaml +@@ -0,0 +1,41 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system ++apiVersion: backstage.io/v1alpha1 ++kind: System ++metadata: ++ name: examples ++spec: ++ owner: guests ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: example-website ++spec: ++ type: website ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ providesApis: [example-grpc-api] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api ++apiVersion: backstage.io/v1alpha1 ++kind: API ++metadata: ++ name: example-grpc-api ++spec: ++ type: grpc ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ definition: | ++ syntax = "proto3"; ++ ++ service Exampler { ++ rpc Example (ExampleMessage) returns (ExampleMessage) {}; ++ } ++ ++ message ExampleMessage { ++ string example = 1; ++ }; +diff --git a/examples/org.yaml b/examples/org.yaml +new file mode 100644 +index 0000000..a10e81f +--- /dev/null ++++ b/examples/org.yaml +@@ -0,0 +1,17 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user ++apiVersion: backstage.io/v1alpha1 ++kind: User ++metadata: ++ name: guest ++spec: ++ memberOf: [guests] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group ++apiVersion: backstage.io/v1alpha1 ++kind: Group ++metadata: ++ name: guests ++spec: ++ type: team ++ children: [] +diff --git a/examples/template/content/catalog-info.yaml b/examples/template/content/catalog-info.yaml +new file mode 100644 +index 0000000..d4ccca4 +--- /dev/null ++++ b/examples/template/content/catalog-info.yaml +@@ -0,0 +1,8 @@ ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: ${{ values.name | dump }} ++spec: ++ type: service ++ owner: user:guest ++ lifecycle: experimental +diff --git a/examples/template/content/index.js b/examples/template/content/index.js +new file mode 100644 +index 0000000..071ce5a +--- /dev/null ++++ b/examples/template/content/index.js +@@ -0,0 +1 @@ ++console.log('Hello from ${{ values.name }}!'); +diff --git a/examples/template/content/package.json b/examples/template/content/package.json +new file mode 100644 +index 0000000..86f968a +--- /dev/null ++++ b/examples/template/content/package.json +@@ -0,0 +1,5 @@ ++{ ++ "name": "${{ values.name }}", ++ "private": true, ++ "dependencies": {} ++} +diff --git a/examples/template/template.yaml b/examples/template/template.yaml +new file mode 100644 +index 0000000..33f262b +--- /dev/null ++++ b/examples/template/template.yaml +@@ -0,0 +1,74 @@ ++apiVersion: scaffolder.backstage.io/v1beta3 ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template ++kind: Template ++metadata: ++ name: example-nodejs-template ++ title: Example Node.js Template ++ description: An example template for the scaffolder that creates a simple Node.js service ++spec: ++ owner: user:guest ++ type: service ++ ++ # These parameters are used to generate the input form in the frontend, and are ++ # used to gather input data for the execution of the template. ++ parameters: ++ - title: Fill in some steps ++ required: ++ - name ++ properties: ++ name: ++ title: Name ++ type: string ++ description: Unique name of the component ++ ui:autofocus: true ++ ui:options: ++ rows: 5 ++ - title: Choose a location ++ required: ++ - repoUrl ++ properties: ++ repoUrl: ++ title: Repository Location ++ type: string ++ ui:field: RepoUrlPicker ++ ui:options: ++ allowedHosts: ++ - github.com ++ ++ # These steps are executed in the scaffolder backend, using data that we gathered ++ # via the parameters above. ++ steps: ++ # Each step executes an action, in this case one templates files into the working directory. ++ - id: fetch-base ++ name: Fetch Base ++ action: fetch:template ++ input: ++ url: ./content ++ values: ++ name: ${{ parameters.name }} ++ ++ # This step publishes the contents of the working directory to GitHub. ++ - id: publish ++ name: Publish ++ action: publish:github ++ input: ++ allowedHosts: ['github.com'] ++ description: This is ${{ parameters.name }} ++ repoUrl: ${{ parameters.repoUrl }} ++ ++ # The final step is to register our new component in the catalog. ++ - id: register ++ name: Register ++ action: catalog:register ++ input: ++ repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} ++ catalogInfoPath: '/catalog-info.yaml' ++ ++ # Outputs are displayed to the user after a successful execution of the template. ++ output: ++ links: ++ - title: Repository ++ url: ${{ steps['publish'].output.remoteUrl }} ++ - title: Open in catalog ++ icon: catalog ++ entityRef: ${{ steps['register'].output.entityRef }} +diff --git a/lerna.json b/lerna.json +index 322929d..529a62f 100644 +--- a/lerna.json ++++ b/lerna.json +@@ -3,4 +3,4 @@ + "npmClient": "yarn", +- "useWorkspaces": true, +- "version": "0.1.0" ++ "version": "0.1.0", ++ "$schema": "node_modules/lerna/schemas/lerna-schema.json" + } +diff --git a/package.json b/package.json +index 82cf726..2364da8 100644 +--- a/package.json ++++ b/package.json +@@ -5,3 +5,3 @@ + "engines": { +- "node": "14 || 16" ++ "node": "18 || 20" + }, +@@ -11,3 +11,4 @@ + "start-backend": "yarn workspace backend start", +- "build": "lerna run build", ++ "build:backend": "yarn workspace backend build", ++ "build:all": "backstage-cli repo build --all", + "build-image": "yarn workspace backend build-image", +@@ -15,15 +16,11 @@ + "tsc:full": "tsc --skipLibCheck false --incremental false", +- "clean": "backstage-cli clean && lerna run clean", +- "diff": "lerna run diff --", +- "test": "backstage-cli test", +- "test:all": "lerna run test -- --coverage", +- "lint": "lerna run lint --since origin/master --", +- "lint:all": "lerna run lint --", ++ "clean": "backstage-cli repo clean", ++ "test": "backstage-cli repo test", ++ "test:all": "backstage-cli repo test --coverage", ++ "test:e2e": "playwright test", ++ "fix": "backstage-cli repo fix", ++ "lint": "backstage-cli repo lint --since origin/master", ++ "lint:all": "backstage-cli repo lint", + "prettier:check": "prettier --check .", +- "create-plugin": "backstage-cli create-plugin --scope internal --no-private", +- "remove-plugin": "backstage-cli remove-plugin" +- }, +- "resolutions": { +- "graphql-language-service-interface": "2.8.2", +- "graphql-language-service-parser": "1.9.0" ++ "new": "backstage-cli new --scope internal" + }, +@@ -36,7 +33,15 @@ + "devDependencies": { +- "@backstage/cli": "^0.8.1", +- "@spotify/prettier-config": "^11.0.0", +- "concurrently": "^6.0.0", +- "lerna": "^4.0.0", +- "prettier": "^2.3.2" ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/e2e-test-utils": "^0.1.0", ++ "@playwright/test": "^1.32.3", ++ "@spotify/prettier-config": "^12.0.0", ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^9.0.0", ++ "prettier": "^2.3.2", ++ "typescript": "~5.2.0" ++ }, ++ "resolutions": { ++ "@types/react": "^17", ++ "@types/react-dom": "^17" + }, +@@ -44,3 +49,3 @@ + "lint-staged": { +- "*.{js,jsx,ts,tsx}": [ ++ "*.{js,jsx,ts,tsx,mjs,cjs}": [ + "eslint --fix", +diff --git a/packages/README.md b/packages/README.md +new file mode 100644 +index 0000000..6327fa0 +--- /dev/null ++++ b/packages/README.md +@@ -0,0 +1,9 @@ ++# The Packages Folder ++ ++This is where your own applications and centrally managed libraries live, each ++in a separate folder of its own. ++ ++From the start there's an `app` folder (for the frontend) and a `backend` folder ++(for the Node backend), but you can also add more modules in here that house ++your core additions and adaptations, such as themes, common React component ++libraries, utilities, and similar. +diff --git a/packages/app/.eslintignore b/packages/app/.eslintignore +new file mode 100644 +index 0000000..a48cf0d +--- /dev/null ++++ b/packages/app/.eslintignore +@@ -0,0 +1 @@ ++public +diff --git a/packages/app/.eslintrc.js b/packages/app/.eslintrc.js +index 13573ef..e2a53a6 100644 +--- a/packages/app/.eslintrc.js ++++ b/packages/app/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/app/cypress.json b/packages/app/cypress.json +deleted file mode 100644 +index 5de7ebf..0000000 +--- a/packages/app/cypress.json ++++ /dev/null +@@ -1,5 +0,0 @@ +-{ +- "baseUrl": "http://localhost:3001", +- "fixturesFolder": false, +- "pluginsFile": false +-} +diff --git a/packages/app/cypress/.eslintrc.json b/packages/app/cypress/.eslintrc.json +deleted file mode 100644 +index 2b3a458..0000000 +--- a/packages/app/cypress/.eslintrc.json ++++ /dev/null +@@ -1,21 +0,0 @@ +-{ +- "plugins": ["cypress"], +- "extends": ["plugin:cypress/recommended"], +- "rules": { +- "jest/expect-expect": [ +- "error", +- { +- "assertFunctionNames": ["expect", "cy.contains"] +- } +- ], +- "import/no-extraneous-dependencies": [ +- "error", +- { +- "devDependencies": true, +- "optionalDependencies": true, +- "peerDependencies": true, +- "bundledDependencies": true +- } +- ] +- } +-} +diff --git a/packages/app/cypress/integration/app.js b/packages/app/cypress/integration/app.js +deleted file mode 100644 +index 43fb2e3..0000000 +--- a/packages/app/cypress/integration/app.js ++++ /dev/null +@@ -1,6 +0,0 @@ +-describe('App', () => { +- it('should render the catalog', () => { +- cy.visit('/'); +- cy.contains('My Company Catalog'); +- }); +-}); +diff --git a/packages/app/e2e-tests/app.test.ts b/packages/app/e2e-tests/app.test.ts +new file mode 100644 +index 0000000..d45bc0d +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,23 @@ ++/* ++ * Copyright 2020 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { test, expect } from '@playwright/test'; ++ ++test('App should render the welcome page', async ({ page }) => { ++ await page.goto('/'); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 0510279..2100405 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,22 +5,38 @@ + "bundled": true, ++ "backstage": { ++ "role": "frontend" ++ }, ++ "scripts": { ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "clean": "backstage-cli package clean", ++ "test": "backstage-cli package test", ++ "lint": "backstage-cli package lint" ++ }, + "dependencies": { +- "@backstage/catalog-model": "^0.9.6", +- "@backstage/cli": "^0.8.1", +- "@backstage/core-app-api": "^0.1.19", +- "@backstage/core-components": "^0.7.2", +- "@backstage/core-plugin-api": "^0.1.12", +- "@backstage/integration-react": "^0.1.13", +- "@backstage/plugin-api-docs": "^0.6.12", +- "@backstage/plugin-catalog": "^0.7.2", +- "@backstage/plugin-catalog-import": "^0.7.3", +- "@backstage/plugin-catalog-react": "^0.6.2", +- "@backstage/plugin-github-actions": "^0.4.22", +- "@backstage/plugin-org": "^0.3.27", +- "@backstage/plugin-scaffolder": "^0.11.9", +- "@backstage/plugin-search": "^0.4.16", +- "@backstage/plugin-tech-radar": "^0.4.11", +- "@backstage/plugin-techdocs": "^0.12.4", +- "@backstage/plugin-user-settings": "^0.3.10", +- "@backstage/test-utils": "^0.1.20", +- "@backstage/theme": "^0.2.12", ++ "@backstage/app-defaults": "^1.4.5-next.1", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/core-app-api": "^1.11.1-next.0", ++ "@backstage/core-components": "^0.13.8-next.1", ++ "@backstage/core-plugin-api": "^1.8.0-next.0", ++ "@backstage/integration-react": "^1.1.21-next.1", ++ "@backstage/plugin-api-docs": "^0.9.13-next.1", ++ "@backstage/plugin-catalog": "^1.15.0-next.1", ++ "@backstage/plugin-catalog-common": "^1.0.17", ++ "@backstage/plugin-catalog-graph": "^0.2.38-next.1", ++ "@backstage/plugin-catalog-import": "^0.10.2-next.1", ++ "@backstage/plugin-catalog-react": "^1.9.0-next.1", ++ "@backstage/plugin-github-actions": "^0.6.7-next.1", ++ "@backstage/plugin-org": "^0.6.16-next.1", ++ "@backstage/plugin-permission-react": "^0.4.17-next.0", ++ "@backstage/plugin-scaffolder": "^1.16.0-next.1", ++ "@backstage/plugin-search": "^1.4.2-next.1", ++ "@backstage/plugin-search-react": "^1.7.2-next.1", ++ "@backstage/plugin-tech-radar": "^0.6.10-next.1", ++ "@backstage/plugin-techdocs": "^1.8.1-next.1", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.2-next.1", ++ "@backstage/plugin-techdocs-react": "^1.1.13-next.1", ++ "@backstage/plugin-user-settings": "^0.7.12-next.1", ++ "@backstage/theme": "^0.4.4-next.0", + "@material-ui/core": "^4.12.2", +@@ -28,30 +44,17 @@ + "history": "^5.0.0", +- "react": "^16.13.1", +- "react-dom": "^16.13.1", +- "react-router": "6.0.0-beta.0", +- "react-router-dom": "6.0.0-beta.0", +- "react-use": "^15.3.3" ++ "react": "^17.0.2", ++ "react-dom": "^17.0.2", ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { ++ "@backstage/test-utils": "^1.4.5-next.0", ++ "@playwright/test": "^1.32.3", + "@testing-library/jest-dom": "^5.10.1", +- "@testing-library/react": "^10.4.1", +- "@testing-library/user-event": "^12.0.7", +- "@types/jest": "^26.0.7", +- "@types/node": "^14.14.32", ++ "@testing-library/react": "^12.1.3", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^8.0.0", + "@types/react-dom": "*", +- "cross-env": "^7.0.0", +- "cypress": "^7.3.0", +- "eslint-plugin-cypress": "^2.10.3", +- "start-server-and-test": "^1.10.11" +- }, +- "scripts": { +- "start": "backstage-cli app:serve", +- "build": "backstage-cli app:build", +- "test": "backstage-cli test", +- "lint": "backstage-cli lint", +- "clean": "backstage-cli clean", +- "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", +- "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", +- "cy:dev": "cypress open", +- "cy:run": "cypress run" ++ "cross-env": "^7.0.0" + }, +diff --git a/packages/app/public/index.html b/packages/app/public/index.html +index b93896c..fc758ee 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -10,3 +10,2 @@ + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index 82bc479..b94cac7 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -12,5 +12,5 @@ describe('App', () => { + app: { title: 'Test' }, +- backend: { baseUrl: 'http://localhost:7000' }, ++ backend: { baseUrl: 'http://localhost:7007' }, + techdocs: { +- storageUrl: 'http://localhost:7000/api/techdocs/static/docs', ++ storageUrl: 'http://localhost:7007/api/techdocs/static/docs', + }, +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 4cd8368..8d62f29 100644 +--- a/packages/app/src/App.tsx ++++ b/packages/app/src/App.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { Navigate, Route } from 'react-router'; ++import { Navigate, Route } from 'react-router-dom'; + import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; +@@ -13,2 +13,3 @@ import { + import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; ++import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +@@ -16,3 +17,2 @@ import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -21,2 +21,4 @@ import { + } from '@backstage/plugin-techdocs'; ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; + import { UserSettingsPage } from '@backstage/plugin-user-settings'; +@@ -28,3 +30,7 @@ import { Root } from './components/Root'; + import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; +-import { createApp, FlatRoutes } from '@backstage/core-app-api'; ++import { createApp } from '@backstage/app-defaults'; ++import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; ++import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; ++import { RequirePermission } from '@backstage/plugin-permission-react'; ++import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; + +@@ -36,5 +42,6 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); + bind(apiDocsPlugin.externalRoutes, { +- createComponent: scaffolderPlugin.routes.root, ++ registerApi: catalogImportPlugin.routes.importPage, + }); +@@ -42,2 +49,6 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, ++ }); ++ bind(orgPlugin.externalRoutes, { ++ catalogIndex: catalogPlugin.routes.catalogIndex, + }); +@@ -46,8 +57,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -59,5 +67,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -72,3 +82,10 @@ const routes = ( + /> +- } /> ++ ++ ++ ++ } ++ /> + }> +@@ -77,2 +94,3 @@ const routes = ( + } /> ++ } /> + +@@ -80,4 +98,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -87,5 +105,3 @@ const App = () => ( + +- ++ , + ); +- +-export default App; +diff --git a/packages/app/src/components/Root/LogoFull.tsx b/packages/app/src/components/Root/LogoFull.tsx +index c7b1c84..47e3b73 100644 +--- a/packages/app/src/components/Root/LogoFull.tsx ++++ b/packages/app/src/components/Root/LogoFull.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/LogoIcon.tsx b/packages/app/src/components/Root/LogoIcon.tsx +index 073cf6e..7eae8c7 100644 +--- a/packages/app/src/components/Root/LogoIcon.tsx ++++ b/packages/app/src/components/Root/LogoIcon.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx +index 198e7ec..6768b48 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,19 +1,3 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- +-import React, { useContext, PropsWithChildren } from 'react'; +-import { Link, makeStyles } from '@material-ui/core'; ++import React, { PropsWithChildren } from 'react'; ++import { makeStyles } from '@material-ui/core'; + import HomeIcon from '@material-ui/icons/Home'; +@@ -25,15 +9,21 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; +-import { Settings as SidebarSettings } from '@backstage/plugin-user-settings'; +-import { SidebarSearch } from '@backstage/plugin-search'; ++import { ++ Settings as SidebarSettings, ++ UserSettingsSignInAvatar, ++} from '@backstage/plugin-user-settings'; ++import { SidebarSearchModal } from '@backstage/plugin-search'; + import { + Sidebar, +- SidebarPage, + sidebarConfig, +- SidebarContext, +- SidebarItem, + SidebarDivider, +- SidebarSpace, ++ SidebarGroup, ++ SidebarItem, ++ SidebarPage, + SidebarScrollWrapper, ++ SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; ++import MenuIcon from '@material-ui/icons/Menu'; ++import SearchIcon from '@material-ui/icons/Search'; + +@@ -56,3 +46,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -60,8 +50,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -76,17 +61,27 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ } to="/search"> ++ ++ + +- {/* Global nav, not org-specific */} +- +- +- +- +- {/* End global nav */} +- +- +- +- ++ }> ++ {/* Global nav, not org-specific */} ++ ++ ++ ++ ++ {/* End global nav */} ++ ++ ++ ++ ++ + + +- ++ } ++ to="/settings" ++ > ++ ++ + +diff --git a/packages/app/src/components/Root/index.ts b/packages/app/src/components/Root/index.ts +index dff706f..3528881 100644 +--- a/packages/app/src/components/Root/index.ts ++++ b/packages/app/src/components/Root/index.ts +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + export { Root } from './Root'; +diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx +index d3b4b78..6722ea2 100644 +--- a/packages/app/src/components/catalog/EntityPage.tsx ++++ b/packages/app/src/components/catalog/EntityPage.tsx +@@ -1,16 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ + import React from 'react'; +@@ -29,3 +14,2 @@ import { + EntityDependsOnResourcesCard, +- EntitySystemDiagramCard, + EntityHasComponentsCard, +@@ -43,2 +27,4 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +@@ -56,2 +42,27 @@ import { EntityTechdocsContent } from '@backstage/plugin-techdocs'; + import { EmptyState } from '@backstage/core-components'; ++import { ++ Direction, ++ EntityCatalogGraphCard, ++} from '@backstage/plugin-catalog-graph'; ++import { ++ RELATION_API_CONSUMED_BY, ++ RELATION_API_PROVIDED_BY, ++ RELATION_CONSUMES_API, ++ RELATION_DEPENDENCY_OF, ++ RELATION_DEPENDS_ON, ++ RELATION_HAS_PART, ++ RELATION_PART_OF, ++ RELATION_PROVIDES_API, ++} from '@backstage/catalog-model'; ++ ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); + +@@ -94,2 +105,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -110,2 +129,6 @@ const overviewContent = ( + ++ ++ ++ ++ + +@@ -152,3 +175,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -179,3 +202,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -198,3 +221,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -225,2 +248,5 @@ const apiPage = ( + ++ ++ ++ + +@@ -292,3 +318,9 @@ const systemPage = ( + +- ++ ++ ++ ++ ++ ++ ++ + +@@ -304,3 +336,19 @@ const systemPage = ( + +- ++ + +@@ -317,2 +365,5 @@ const domainPage = ( + ++ ++ ++ + +diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx +index 7b3c2b2..1788dde 100644 +--- a/packages/app/src/components/search/SearchPage.tsx ++++ b/packages/app/src/components/search/SearchPage.tsx +@@ -1,5 +1,12 @@ + import React from 'react'; +-import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; ++import { makeStyles, Theme, Grid, Paper } from '@material-ui/core'; + +-import { CatalogResultListItem } from '@backstage/plugin-catalog'; ++import { CatalogSearchResultListItem } from '@backstage/plugin-catalog'; ++import { ++ catalogApiRef, ++ CATALOG_FILTER_EXISTS, ++} from '@backstage/plugin-catalog-react'; ++import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; ++ ++import { SearchType } from '@backstage/plugin-search'; + import { +@@ -8,5 +15,13 @@ import { + SearchResult, +- DefaultResultListItem, +-} from '@backstage/plugin-search'; +-import { Content, Header, Page } from '@backstage/core-components'; ++ SearchPagination, ++ useSearch, ++} from '@backstage/plugin-search-react'; ++import { ++ CatalogIcon, ++ Content, ++ DocsIcon, ++ Header, ++ Page, ++} from '@backstage/core-components'; ++import { useApi } from '@backstage/core-plugin-api'; + +@@ -18,2 +33,3 @@ const useStyles = makeStyles((theme: Theme) => ({ + padding: theme.spacing(2), ++ marginTop: theme.spacing(2), + }, +@@ -28,2 +44,4 @@ const SearchPage = () => { + const classes = useStyles(); ++ const { types } = useSearch(); ++ const catalogApi = useApi(catalogApiRef); + +@@ -36,3 +54,3 @@ const SearchPage = () => { + +- ++ + +@@ -40,5 +58,43 @@ const SearchPage = () => { + ++ , ++ }, ++ { ++ value: 'techdocs', ++ name: 'Documentation', ++ icon: , ++ }, ++ ]} ++ /> + ++ {types.includes('techdocs') && ( ++ { ++ // Return a list of entities which are documented. ++ const { items } = await catalogApi.getEntities({ ++ fields: ['metadata.name'], ++ filter: { ++ 'metadata.annotations.backstage.io/techdocs-ref': ++ CATALOG_FILTER_EXISTS, ++ }, ++ }); ++ ++ const names = items.map(entity => entity.metadata.name); ++ names.sort(); ++ return names; ++ }} ++ /> ++ )} + { + className={classes.filter} ++ label="Lifecycle" + name="lifecycle" +@@ -54,25 +111,6 @@ const SearchPage = () => { + ++ + +- {({ results }) => ( +- +- {results.map(({ type, document }) => { +- switch (type) { +- case 'software-catalog': +- return ( +- +- ); +- default: +- return ( +- +- ); +- } +- })} +- +- )} ++ } /> ++ } /> + +diff --git a/packages/backend/.eslintrc.js b/packages/backend/.eslintrc.js +index 16a033d..e2a53a6 100644 +--- a/packages/backend/.eslintrc.js ++++ b/packages/backend/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint.backend')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index 31231a3..18548e9 100644 +--- a/packages/backend/Dockerfile ++++ b/packages/backend/Dockerfile +@@ -7,3 +7,3 @@ + # yarn tsc +-# yarn build ++# yarn build:backend + # +@@ -11,6 +11,29 @@ + +-FROM node:14-buster-slim ++FROM node:18-bookworm-slim + ++# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends python3 g++ build-essential && \ ++ yarn config set python /usr/bin/python3 ++ ++# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, ++# in which case you should also move better-sqlite3 to "devDependencies" in package.json. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends libsqlite3-dev ++ ++# From here on we use the least-privileged `node` user to run the backend. ++USER node ++ ++# This should create the app dir as `node`. ++# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. ++# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. + WORKDIR /app + ++# This switches many Node.js dependencies to production mode. ++ENV NODE_ENV production ++ + # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. +@@ -18,11 +41,12 @@ WORKDIR /app + # and along with yarn.lock and the root package.json, that's enough to run yarn install. +-COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ ++COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ + RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz + +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + + # Then copy the rest of the backend bundle, along with any other files we might want. +-COPY packages/backend/dist/bundle.tar.gz app-config.yaml ./ ++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"] +diff --git a/packages/backend/README.md b/packages/backend/README.md +index 81e0f80..3607b0a 100644 +--- a/packages/backend/README.md ++++ b/packages/backend/README.md +@@ -18,4 +18,2 @@ To run the example backend, first go to the project root and run + yarn install +-yarn tsc +-yarn build + ``` +@@ -27,7 +25,2 @@ After that, go to the `packages/backend` directory and run + ```bash +-AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x \ +-AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x \ +-AUTH_OAUTH2_CLIENT_ID=x AUTH_OAUTH2_CLIENT_SECRET=x \ +-AUTH_OAUTH2_AUTH_URL=x AUTH_OAUTH2_TOKEN_URL=x \ +-LOG_LEVEL=debug \ + yarn start +@@ -35,6 +28,6 @@ yarn start + +-Substitute `x` for actual values, or leave them as dummy values just to try out +-the backend without using the auth or sentry features. ++If you want to override any configuration locally, for example adding any secrets, ++you can do so in `app-config.local.yaml`. + +-The backend starts up on port 7000 per default. ++The backend starts up on port 7007 per default. + +@@ -45,3 +38,3 @@ locations to the backend. These are places where the backend can find some + entity descriptor data to consume and serve. For more information, see +-[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog). ++[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). + +@@ -65,2 +58,2 @@ and + - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +-- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) ++- [Backstage Documentation](https://backstage.io/docs) +diff --git a/packages/backend/package.json b/packages/backend/package.json +index bd338cc..f224eaf 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,32 +6,37 @@ + "private": true, ++ "backstage": { ++ "role": "backend" ++ }, + "scripts": { +- "build": "backstage-cli backend:bundle", +- "build-image": "docker build ../.. -f Dockerfile --tag backstage", +- "start": "backstage-cli backend:dev", +- "lint": "backstage-cli lint", +- "test": "backstage-cli test", +- "clean": "backstage-cli clean", +- "migrate:create": "knex migrate:make -x ts" ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "lint": "backstage-cli package lint", ++ "test": "backstage-cli package test", ++ "clean": "backstage-cli package clean", ++ "build-image": "docker build ../.. -f Dockerfile --tag backstage" + }, + "dependencies": { +- "app": "0.0.0", +- "@backstage/backend-common": "^0.9.8", +- "@backstage/catalog-model": "^0.9.6", +- "@backstage/catalog-client": "^0.5.0", +- "@backstage/config": "^0.1.11", +- "@backstage/plugin-app-backend": "^0.3.18", +- "@backstage/plugin-auth-backend": "^0.4.6", +- "@backstage/plugin-catalog-backend": "^0.17.2", +- "@backstage/plugin-proxy-backend": "^0.2.13", +- "@backstage/plugin-scaffolder-backend": "^0.15.11", +- "@backstage/plugin-search-backend": "^0.2.6", +- "@backstage/plugin-search-backend-node": "^0.4.3", +- "@backstage/plugin-techdocs-backend": "^0.10.6", +- "@gitbeaker/node": "^30.2.0", +- "@octokit/rest": "^18.5.3", +- "dockerode": "^3.2.1", ++ "@backstage/backend-common": "^0.19.9-next.1", ++ "@backstage/backend-tasks": "^0.5.12-next.1", ++ "@backstage/catalog-client": "^1.4.5", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/config": "^1.1.1", ++ "@backstage/plugin-app-backend": "^0.3.55-next.1", ++ "@backstage/plugin-auth-backend": "^0.20.0-next.1", ++ "@backstage/plugin-auth-node": "^0.4.1-next.1", ++ "@backstage/plugin-catalog-backend": "^1.15.0-next.1", ++ "@backstage/plugin-permission-common": "^0.7.9", ++ "@backstage/plugin-permission-node": "^0.7.18-next.1", ++ "@backstage/plugin-proxy-backend": "^0.4.5-next.1", ++ "@backstage/plugin-scaffolder-backend": "^1.19.0-next.1", ++ "@backstage/plugin-search-backend": "^1.4.7-next.1", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.16-next.1", ++ "@backstage/plugin-search-backend-node": "^1.2.11-next.1", ++ "@backstage/plugin-techdocs-backend": "^1.9.0-next.1", ++ "app": "link:../app", ++ "better-sqlite3": "^8.0.0", ++ "dockerode": "^3.3.1", + "express": "^4.17.1", + "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "pg": "^8.3.0", + "winston": "^3.2.1" +@@ -39,6 +44,7 @@ + "devDependencies": { +- "@backstage/cli": "^0.8.1", +- "@types/dockerode": "^3.2.1", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@types/dockerode": "^3.3.0", + "@types/express": "^4.17.6", +- "@types/express-serve-static-core": "^4.17.5" ++ "@types/express-serve-static-core": "^4.17.5", ++ "@types/luxon": "^2.0.4" + }, +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index f2b14b2..04c4ff9 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -17,5 +17,7 @@ import { + DatabaseManager, +- SingleHostDiscovery, ++ HostDiscovery, + UrlReaders, ++ ServerTokenManager, + } from '@backstage/backend-common'; ++import { TaskScheduler } from '@backstage/backend-tasks'; + import { Config } from '@backstage/config'; +@@ -29,2 +31,4 @@ import search from './plugins/search'; + import { PluginEnvironment } from './types'; ++import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; + +@@ -33,8 +37,17 @@ function makeCreateEnv(config: Config) { + const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++ const discovery = HostDiscovery.fromConfig(config); ++ const cacheManager = CacheManager.fromConfig(config); ++ const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); ++ const tokenManager = ServerTokenManager.noop(); ++ const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); + +- root.info(`Created UrlReader ${reader}`); ++ const identity = DefaultIdentityClient.create({ ++ discovery, ++ }); ++ const permissions = ServerPermissionClient.fromConfig(config, { ++ discovery, ++ tokenManager, ++ }); + +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++ root.info(`Created UrlReader ${reader}`); + +@@ -44,3 +57,15 @@ function makeCreateEnv(config: Config) { + const cache = cacheManager.forPlugin(plugin); +- return { logger, database, cache, config, reader, discovery }; ++ const scheduler = taskScheduler.forPlugin(plugin); ++ return { ++ logger, ++ database, ++ cache, ++ config, ++ reader, ++ discovery, ++ tokenManager, ++ scheduler, ++ permissions, ++ identity, ++ }; + }; +@@ -70,2 +95,4 @@ async function main() { + apiRouter.use('/search', await search(searchEnv)); ++ ++ // Add backends ABOVE this line; this 404 handler is the catch-all fallback + apiRouter.use(notFoundHandler()); +@@ -85,3 +112,3 @@ module.hot?.accept(); + main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); ++ console.error('Backend failed to start up', error); + process.exit(1); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +index 07fb04f..7c37f68 100644 +--- a/packages/backend/src/plugins/app.ts ++++ b/packages/backend/src/plugins/app.ts +@@ -4,9 +4,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + return await createRouter({ +- logger, +- config, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, + appPackageName: 'app', +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +index 5216510..77eb6aa 100644 +--- a/packages/backend/src/plugins/auth.ts ++++ b/packages/backend/src/plugins/auth.ts +@@ -1,2 +1,6 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; ++import { ++ createRouter, ++ providers, ++ defaultAuthProviderFactories, ++} from '@backstage/plugin-auth-backend'; + import { Router } from 'express'; +@@ -4,9 +8,47 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, database, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, ++ providerFactories: { ++ ...defaultAuthProviderFactories, ++ ++ // This replaces the default GitHub auth provider with a customized one. ++ // The `signIn` option enables sign-in for this provider, using the ++ // identity resolution logic that's provided in the `resolver` callback. ++ // ++ // This particular resolver makes all users share a single "guest" identity. ++ // It should only be used for testing and trying out Backstage. ++ // ++ // If you want to use a production ready resolver you can switch to ++ // the one that is commented out below, it looks up a user entity in the ++ // catalog using the GitHub username of the authenticated user. ++ // That resolver requires you to have user entities populated in the catalog, ++ // for example using https://backstage.io/docs/integrations/github/org ++ // ++ // There are other resolvers to choose from, and you can also create ++ // your own, see the auth documentation for more details: ++ // ++ // https://backstage.io/docs/auth/identity-resolver ++ github: providers.github.create({ ++ signIn: { ++ resolver(_, ctx) { ++ const userRef = 'user:default/guest'; // Must be a full entity reference ++ return ctx.issueToken({ ++ claims: { ++ sub: userRef, // The user's own identity ++ ent: [userRef], // A list of identities that the user claims ownership through ++ }, ++ }); ++ }, ++ // resolver: providers.github.resolvers.usernameMatchingUserEntityName(), ++ }, ++ }), ++ }, ++ }); + } +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +index 506f6d9..54ec393 100644 +--- a/packages/backend/src/plugins/proxy.ts ++++ b/packages/backend/src/plugins/proxy.ts +@@ -4,8 +4,10 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ }); + } +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +index 6be2e97..a12fee2 100644 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ b/packages/backend/src/plugins/scaffolder.ts +@@ -1,5 +1,3 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; + import { CatalogClient } from '@backstage/catalog-client'; + import { createRouter } from '@backstage/plugin-scaffolder-backend'; +-import Docker from 'dockerode'; + import { Router } from 'express'; +@@ -7,20 +5,17 @@ import type { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- database, +- reader, +- discovery, +-}: PluginEnvironment): Promise { +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- const catalogClient = new CatalogClient({ discoveryApi: discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ const catalogClient = new CatalogClient({ ++ discoveryApi: env.discovery, ++ }); + + return await createRouter({ +- containerRunner, +- logger, +- config, +- database, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ reader: env.reader, + catalogClient, +- reader, ++ identity: env.identity, ++ permissions: env.permissions, + }); +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +index 7fc317d..e9469dc 100644 +--- a/packages/backend/src/plugins/search.ts ++++ b/packages/backend/src/plugins/search.ts +@@ -7,18 +7,44 @@ import { + import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; ++import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend'; ++import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; ++import { Router } from 'express'; + +-export default async function createPlugin({ +- logger, +- discovery, +- config, +-}: PluginEnvironment) { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); ++ const searchEngine = new LunrSearchEngine({ ++ logger: env.logger, ++ }); ++ const indexBuilder = new IndexBuilder({ ++ logger: env.logger, ++ searchEngine, ++ }); ++ ++ const schedule = env.scheduler.createScheduledTaskRunner({ ++ frequency: { minutes: 10 }, ++ timeout: { minutes: 15 }, ++ // A 3 second delay gives the backend server a chance to initialize before ++ // any collators are executed, which may attempt requests against the API. ++ initialDelay: { seconds: 3 }, ++ }); + + // Collators are responsible for gathering documents known to plugins. This +- // particular collator gathers entities from the software catalog. ++ // collator gathers entities from the software catalog. ++ indexBuilder.addCollator({ ++ schedule, ++ factory: DefaultCatalogCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, ++ }), ++ }); ++ ++ // collator gathers entities from techdocs. + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { discovery }), ++ schedule, ++ factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ logger: env.logger, ++ tokenManager: env.tokenManager, ++ }), + }); +@@ -28,6 +54,4 @@ export default async function createPlugin({ + const { scheduler } = await indexBuilder.build(); ++ scheduler.start(); + +- // A 3 second delay gives the backend server a chance to initialize before +- // any collators are executed, which may attempt requests against the API. +- setTimeout(() => scheduler.start(), 3000); + useHotCleanup(module, () => scheduler.stop()); +@@ -36,3 +60,6 @@ export default async function createPlugin({ + engine: indexBuilder.getSearchEngine(), +- logger, ++ types: indexBuilder.getDocumentTypes(), ++ permissions: env.permissions, ++ config: env.config, ++ logger: env.logger, + }); +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +index 906d86d..be8bb0c 100644 +--- a/packages/backend/src/plugins/techdocs.ts ++++ b/packages/backend/src/plugins/techdocs.ts +@@ -11,12 +11,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, ++ const preparers = await Preparers.fromConfig(env.config, { ++ logger: env.logger, ++ reader: env.reader, + }); +@@ -28,4 +25,4 @@ export default async function createPlugin({ + // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, ++ const generators = await Generators.fromConfig(env.config, { ++ logger: env.logger, + containerRunner, +@@ -36,5 +33,5 @@ export default async function createPlugin({ + // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, ++ const publisher = await Publisher.fromConfig(env.config, { ++ logger: env.logger, ++ discovery: env.discovery, + }); +@@ -48,5 +45,6 @@ export default async function createPlugin({ + publisher, +- logger, +- config, +- discovery, ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ cache: env.cache, + }); +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +index 6c78a2a..9cd2c74 100644 +--- a/packages/backend/src/types.ts ++++ b/packages/backend/src/types.ts +@@ -6,4 +6,8 @@ import { + PluginEndpointDiscovery, ++ TokenManager, + UrlReader, + } from '@backstage/backend-common'; ++import { PluginTaskScheduler } from '@backstage/backend-tasks'; ++import { PermissionEvaluator } from '@backstage/plugin-permission-common'; ++import { IdentityApi } from '@backstage/plugin-auth-node'; + +@@ -16,2 +20,6 @@ export type PluginEnvironment = { + discovery: PluginEndpointDiscovery; ++ tokenManager: TokenManager; ++ scheduler: PluginTaskScheduler; ++ permissions: PermissionEvaluator; ++ identity: IdentityApi; + }; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..37c7fb1 +--- /dev/null ++++ b/playwright.config.ts +@@ -0,0 +1,60 @@ ++/* ++ * Copyright 2023 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { defineConfig } from '@playwright/test'; ++import { generateProjects } from '@backstage/e2e-test-utils/playwright'; ++ ++/** ++ * See https://playwright.dev/docs/test-configuration. ++ */ ++export default defineConfig({ ++ timeout: 60_000, ++ ++ expect: { ++ timeout: 5_000, ++ }, ++ ++ // Run your local dev server before starting the tests ++ webServer: process.env.CI ++ ? [] ++ : [ ++ { ++ command: 'yarn start', ++ port: 3000, ++ reuseExistingServer: true, ++ timeout: 60_000, ++ }, ++ ], ++ ++ forbidOnly: !!process.env.CI, ++ ++ retries: process.env.CI ? 2 : 0, ++ ++ reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]], ++ ++ use: { ++ actionTimeout: 0, ++ baseURL: ++ process.env.PLAYWRIGHT_URL ?? ++ (process.env.CI ? 'http://localhost:7007' : 'http://localhost:3000'), ++ screenshot: 'only-on-failure', ++ trace: 'on-first-retry', ++ }, ++ ++ outputDir: 'node_modules/.cache/e2e-test-results', ++ ++ projects: generateProjects(), // Find all packages with e2e-test folders ++}); +diff --git a/plugins/README.md b/plugins/README.md +new file mode 100644 +index 0000000..d7865fd +--- /dev/null ++++ b/plugins/README.md +@@ -0,0 +1,9 @@ ++# The Plugins Folder ++ ++This is where your own plugins and their associated modules live, each in a ++separate folder of its own. ++ ++If you want to create a new plugin here, go to your project root directory, run ++the command `yarn new`, and follow the on-screen instructions. ++ ++You can also check out existing plugins on [the plugin marketplace](https://backstage.io/plugins)! +diff --git a/yarn.lock b/yarn.lock +new file mode 100644 +index 0000000..5ad7fe2 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1 @@ ++# intentionally left empty diff --git a/diffs/0.4.10..0.5.7-next.1.diff b/diffs/0.4.10..0.5.7-next.1.diff new file mode 100644 index 00000000..b33daea2 --- /dev/null +++ b/diffs/0.4.10..0.5.7-next.1.diff @@ -0,0 +1,1833 @@ +diff --git a/.dockerignore b/.dockerignore +index 63c9c34..05edb62 100644 +--- a/.dockerignore ++++ b/.dockerignore +@@ -1,5 +1,8 @@ + .git ++.yarn/cache ++.yarn/install-state.gz + node_modules +-packages +-!packages/backend/dist ++packages/*/src ++packages/*/node_modules + plugins ++*.local.yaml +diff --git a/.eslintignore b/.eslintignore +new file mode 100644 +index 0000000..e5b1994 +--- /dev/null ++++ b/.eslintignore +@@ -0,0 +1 @@ ++playwright.config.ts +diff --git a/.gitignore b/.gitignore +index d16a8d3..fbf8139 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -17,2 +17,11 @@ node_modules/ + ++# Yarn 3 files ++.pnp.* ++.yarn/* ++!.yarn/patches ++!.yarn/plugins ++!.yarn/releases ++!.yarn/sdks ++!.yarn/versions ++ + # Node version directives +@@ -39 +48,7 @@ site + *-credentials.yaml ++ ++# vscode database functionality support files ++*.session.sql ++ ++# E2E test reports ++e2e-test-report/ +diff --git a/app-config.production.yaml b/app-config.production.yaml +index 5e36c23..8f0751c 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -1,3 +1,3 @@ + app: +- # Should be the same as backend.baseUrl when using the `app-backend` plugin ++ # Should be the same as backend.baseUrl when using the `app-backend` plugin. + baseUrl: http://localhost:7007 +@@ -5,4 +5,31 @@ app: + backend: ++ # Note that the baseUrl should be the URL that the browser and other clients ++ # should use when communicating with the backend, i.e. it needs to be ++ # reachable not just from within the backend host, but from all of your ++ # callers. When its value is "http://localhost:7007", it's strictly private ++ # and can't be reached by others. + baseUrl: http://localhost:7007 +- listen: +- port: 7007 ++ # The listener can also be expressed as a single : string. In this case we bind to ++ # all interfaces, the most permissive setting. The right value depends on your specific deployment. ++ listen: ':7007' ++ ++ # config options: https://node-postgres.com/api/client ++ database: ++ client: pg ++ connection: ++ host: ${POSTGRES_HOST} ++ port: ${POSTGRES_PORT} ++ user: ${POSTGRES_USER} ++ password: ${POSTGRES_PASSWORD} ++ # https://node-postgres.com/features/ssl ++ # you can set the sslmode configuration option via the `PGSSLMODE` environment variable ++ # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require) ++ # ssl: ++ # ca: # if you have a CA file and want to verify it you can uncomment this section ++ # $file: /ca/server.crt ++ ++catalog: ++ # Overrides the default list locations from app-config.yaml as these contain example data. ++ # See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details ++ # on how to get entities into the catalog. ++ locations: [] +diff --git a/app-config.yaml b/app-config.yaml +index 1a622a2..08d2abf 100644 +--- a/app-config.yaml ++++ b/app-config.yaml +@@ -9,3 +9,4 @@ backend: + # Used for enabling authentication, secret is shared by all backend plugins +- # See backend-to-backend-auth.md in the docs for information on the format ++ # See https://backstage.io/docs/auth/service-to-service-auth for ++ # information on the format + # auth: +@@ -16,2 +17,4 @@ backend: + port: 7007 ++ # Uncomment the following host directive to bind to specific interfaces ++ # host: 127.0.0.1 + csp: +@@ -22,9 +25,9 @@ backend: + origin: http://localhost:3000 +- methods: [GET, POST, PUT, DELETE] ++ methods: [GET, HEAD, PATCH, POST, PUT, DELETE] + credentials: true ++ # This is for local development only, it is not recommended to use this in production ++ # The production database configuration is stored in app-config.production.yaml + database: +- client: sqlite3 ++ client: better-sqlite3 + connection: ':memory:' +- cache: +- store: memory + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir +@@ -34,2 +37,4 @@ integrations: + - host: github.com ++ # This is a Personal Access Token or PAT from GitHub. You can find out how to generate this token, and more information ++ # about setting up the GitHub integration here: https://backstage.io/docs/getting-started/configuration#setting-up-a-github-integration + token: ${GITHUB_TOKEN} +@@ -41,5 +46,8 @@ integrations: + proxy: +- '/test': +- target: 'https://example.com' +- changeOrigin: true ++ ### Example for how to add a proxy endpoint for the frontend. ++ ### A typical reason to do this is to handle HTTPS and CORS for internal services. ++ # endpoints: ++ # '/test': ++ # target: 'https://example.com' ++ # changeOrigin: true + +@@ -64,41 +72,32 @@ scaffolder: + catalog: ++ import: ++ entityFilename: catalog-info.yaml ++ pullRequestBranchName: backstage-integration + rules: +- - allow: [Component, System, API, Group, User, Resource, Location] ++ - allow: [Component, System, API, Resource, Location] + locations: +- # Backstage example components +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-components.yaml ++ # Local example data, file locations are relative to the backend process, typically `packages/backend` ++ - type: file ++ target: ../../examples/entities.yaml + +- # Backstage example systems +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-systems.yaml +- +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml +- +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml +- +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.yaml +- +- # Backstage example templates +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/react-ssr-template/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/springboot-grpc-template/template.yaml ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml + rules: + - allow: [Template] +- - type: url +- target: https://github.com/spotify/cookiecutter-golang/blob/master/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/docs-template/template.yaml ++ ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml + rules: +- - allow: [Template] ++ - allow: [User, Group] ++ ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml ++ ++ ## Uncomment these lines to add an example org ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme-corp.yaml ++ # rules: ++ # - allow: [User, Group] +diff --git a/backstage.json b/backstage.json +index da31324..707af29 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "0.4.10" ++ "version": "1.20.0-next.1" + } +diff --git a/examples/entities.yaml b/examples/entities.yaml +new file mode 100644 +index 0000000..447e8b1 +--- /dev/null ++++ b/examples/entities.yaml +@@ -0,0 +1,41 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system ++apiVersion: backstage.io/v1alpha1 ++kind: System ++metadata: ++ name: examples ++spec: ++ owner: guests ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: example-website ++spec: ++ type: website ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ providesApis: [example-grpc-api] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api ++apiVersion: backstage.io/v1alpha1 ++kind: API ++metadata: ++ name: example-grpc-api ++spec: ++ type: grpc ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ definition: | ++ syntax = "proto3"; ++ ++ service Exampler { ++ rpc Example (ExampleMessage) returns (ExampleMessage) {}; ++ } ++ ++ message ExampleMessage { ++ string example = 1; ++ }; +diff --git a/examples/org.yaml b/examples/org.yaml +new file mode 100644 +index 0000000..a10e81f +--- /dev/null ++++ b/examples/org.yaml +@@ -0,0 +1,17 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user ++apiVersion: backstage.io/v1alpha1 ++kind: User ++metadata: ++ name: guest ++spec: ++ memberOf: [guests] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group ++apiVersion: backstage.io/v1alpha1 ++kind: Group ++metadata: ++ name: guests ++spec: ++ type: team ++ children: [] +diff --git a/examples/template/content/catalog-info.yaml b/examples/template/content/catalog-info.yaml +new file mode 100644 +index 0000000..d4ccca4 +--- /dev/null ++++ b/examples/template/content/catalog-info.yaml +@@ -0,0 +1,8 @@ ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: ${{ values.name | dump }} ++spec: ++ type: service ++ owner: user:guest ++ lifecycle: experimental +diff --git a/examples/template/content/index.js b/examples/template/content/index.js +new file mode 100644 +index 0000000..071ce5a +--- /dev/null ++++ b/examples/template/content/index.js +@@ -0,0 +1 @@ ++console.log('Hello from ${{ values.name }}!'); +diff --git a/examples/template/content/package.json b/examples/template/content/package.json +new file mode 100644 +index 0000000..86f968a +--- /dev/null ++++ b/examples/template/content/package.json +@@ -0,0 +1,5 @@ ++{ ++ "name": "${{ values.name }}", ++ "private": true, ++ "dependencies": {} ++} +diff --git a/examples/template/template.yaml b/examples/template/template.yaml +new file mode 100644 +index 0000000..33f262b +--- /dev/null ++++ b/examples/template/template.yaml +@@ -0,0 +1,74 @@ ++apiVersion: scaffolder.backstage.io/v1beta3 ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template ++kind: Template ++metadata: ++ name: example-nodejs-template ++ title: Example Node.js Template ++ description: An example template for the scaffolder that creates a simple Node.js service ++spec: ++ owner: user:guest ++ type: service ++ ++ # These parameters are used to generate the input form in the frontend, and are ++ # used to gather input data for the execution of the template. ++ parameters: ++ - title: Fill in some steps ++ required: ++ - name ++ properties: ++ name: ++ title: Name ++ type: string ++ description: Unique name of the component ++ ui:autofocus: true ++ ui:options: ++ rows: 5 ++ - title: Choose a location ++ required: ++ - repoUrl ++ properties: ++ repoUrl: ++ title: Repository Location ++ type: string ++ ui:field: RepoUrlPicker ++ ui:options: ++ allowedHosts: ++ - github.com ++ ++ # These steps are executed in the scaffolder backend, using data that we gathered ++ # via the parameters above. ++ steps: ++ # Each step executes an action, in this case one templates files into the working directory. ++ - id: fetch-base ++ name: Fetch Base ++ action: fetch:template ++ input: ++ url: ./content ++ values: ++ name: ${{ parameters.name }} ++ ++ # This step publishes the contents of the working directory to GitHub. ++ - id: publish ++ name: Publish ++ action: publish:github ++ input: ++ allowedHosts: ['github.com'] ++ description: This is ${{ parameters.name }} ++ repoUrl: ${{ parameters.repoUrl }} ++ ++ # The final step is to register our new component in the catalog. ++ - id: register ++ name: Register ++ action: catalog:register ++ input: ++ repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} ++ catalogInfoPath: '/catalog-info.yaml' ++ ++ # Outputs are displayed to the user after a successful execution of the template. ++ output: ++ links: ++ - title: Repository ++ url: ${{ steps['publish'].output.remoteUrl }} ++ - title: Open in catalog ++ icon: catalog ++ entityRef: ${{ steps['register'].output.entityRef }} +diff --git a/lerna.json b/lerna.json +index 322929d..529a62f 100644 +--- a/lerna.json ++++ b/lerna.json +@@ -3,4 +3,4 @@ + "npmClient": "yarn", +- "useWorkspaces": true, +- "version": "0.1.0" ++ "version": "0.1.0", ++ "$schema": "node_modules/lerna/schemas/lerna-schema.json" + } +diff --git a/package.json b/package.json +index f423117..2364da8 100644 +--- a/package.json ++++ b/package.json +@@ -5,3 +5,3 @@ + "engines": { +- "node": "14 || 16" ++ "node": "18 || 20" + }, +@@ -11,3 +11,4 @@ + "start-backend": "yarn workspace backend start", +- "build": "lerna run build", ++ "build:backend": "yarn workspace backend build", ++ "build:all": "backstage-cli repo build --all", + "build-image": "yarn workspace backend build-image", +@@ -15,11 +16,11 @@ + "tsc:full": "tsc --skipLibCheck false --incremental false", +- "clean": "backstage-cli clean && lerna run clean", +- "diff": "lerna run diff --", +- "test": "backstage-cli test", +- "test:all": "lerna run test -- --coverage", +- "lint": "lerna run lint --since origin/master --", +- "lint:all": "lerna run lint --", ++ "clean": "backstage-cli repo clean", ++ "test": "backstage-cli repo test", ++ "test:all": "backstage-cli repo test --coverage", ++ "test:e2e": "playwright test", ++ "fix": "backstage-cli repo fix", ++ "lint": "backstage-cli repo lint --since origin/master", ++ "lint:all": "backstage-cli repo lint", + "prettier:check": "prettier --check .", +- "create-plugin": "backstage-cli create-plugin --scope internal", +- "remove-plugin": "backstage-cli remove-plugin" ++ "new": "backstage-cli new --scope internal" + }, +@@ -32,7 +33,15 @@ + "devDependencies": { +- "@backstage/cli": "^0.10.4", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/e2e-test-utils": "^0.1.0", ++ "@playwright/test": "^1.32.3", + "@spotify/prettier-config": "^12.0.0", +- "concurrently": "^6.0.0", +- "lerna": "^4.0.0", +- "prettier": "^2.3.2" ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^9.0.0", ++ "prettier": "^2.3.2", ++ "typescript": "~5.2.0" ++ }, ++ "resolutions": { ++ "@types/react": "^17", ++ "@types/react-dom": "^17" + }, +diff --git a/packages/README.md b/packages/README.md +new file mode 100644 +index 0000000..6327fa0 +--- /dev/null ++++ b/packages/README.md +@@ -0,0 +1,9 @@ ++# The Packages Folder ++ ++This is where your own applications and centrally managed libraries live, each ++in a separate folder of its own. ++ ++From the start there's an `app` folder (for the frontend) and a `backend` folder ++(for the Node backend), but you can also add more modules in here that house ++your core additions and adaptations, such as themes, common React component ++libraries, utilities, and similar. +diff --git a/packages/app/.eslintignore b/packages/app/.eslintignore +new file mode 100644 +index 0000000..a48cf0d +--- /dev/null ++++ b/packages/app/.eslintignore +@@ -0,0 +1 @@ ++public +diff --git a/packages/app/.eslintrc.js b/packages/app/.eslintrc.js +index 13573ef..e2a53a6 100644 +--- a/packages/app/.eslintrc.js ++++ b/packages/app/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/app/cypress.json b/packages/app/cypress.json +deleted file mode 100644 +index 5de7ebf..0000000 +--- a/packages/app/cypress.json ++++ /dev/null +@@ -1,5 +0,0 @@ +-{ +- "baseUrl": "http://localhost:3001", +- "fixturesFolder": false, +- "pluginsFile": false +-} +diff --git a/packages/app/cypress/.eslintrc.json b/packages/app/cypress/.eslintrc.json +deleted file mode 100644 +index 2b3a458..0000000 +--- a/packages/app/cypress/.eslintrc.json ++++ /dev/null +@@ -1,21 +0,0 @@ +-{ +- "plugins": ["cypress"], +- "extends": ["plugin:cypress/recommended"], +- "rules": { +- "jest/expect-expect": [ +- "error", +- { +- "assertFunctionNames": ["expect", "cy.contains"] +- } +- ], +- "import/no-extraneous-dependencies": [ +- "error", +- { +- "devDependencies": true, +- "optionalDependencies": true, +- "peerDependencies": true, +- "bundledDependencies": true +- } +- ] +- } +-} +diff --git a/packages/app/cypress/integration/app.js b/packages/app/cypress/integration/app.js +deleted file mode 100644 +index 43fb2e3..0000000 +--- a/packages/app/cypress/integration/app.js ++++ /dev/null +@@ -1,6 +0,0 @@ +-describe('App', () => { +- it('should render the catalog', () => { +- cy.visit('/'); +- cy.contains('My Company Catalog'); +- }); +-}); +diff --git a/packages/app/e2e-tests/app.test.ts b/packages/app/e2e-tests/app.test.ts +new file mode 100644 +index 0000000..d45bc0d +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,23 @@ ++/* ++ * Copyright 2020 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { test, expect } from '@playwright/test'; ++ ++test('App should render the welcome page', async ({ page }) => { ++ await page.goto('/'); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 9b9ea2f..2100405 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,22 +5,38 @@ + "bundled": true, ++ "backstage": { ++ "role": "frontend" ++ }, ++ "scripts": { ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "clean": "backstage-cli package clean", ++ "test": "backstage-cli package test", ++ "lint": "backstage-cli package lint" ++ }, + "dependencies": { +- "@backstage/app-defaults": "^0.1.3", +- "@backstage/catalog-model": "^0.9.8", +- "@backstage/cli": "^0.10.4", +- "@backstage/core-app-api": "^0.3.0", +- "@backstage/core-components": "^0.8.2", +- "@backstage/core-plugin-api": "^0.4.0", +- "@backstage/integration-react": "^0.1.17", +- "@backstage/plugin-api-docs": "^0.6.20", +- "@backstage/plugin-catalog": "^0.7.6", +- "@backstage/plugin-catalog-import": "^0.7.7", +- "@backstage/plugin-catalog-react": "^0.6.9", +- "@backstage/plugin-github-actions": "^0.4.29", +- "@backstage/plugin-org": "^0.3.32", +- "@backstage/plugin-scaffolder": "^0.11.16", +- "@backstage/plugin-search": "^0.5.3", +- "@backstage/plugin-tech-radar": "^0.5.0", +- "@backstage/plugin-techdocs": "^0.12.12", +- "@backstage/plugin-user-settings": "^0.3.14", +- "@backstage/theme": "^0.2.14", ++ "@backstage/app-defaults": "^1.4.5-next.1", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/core-app-api": "^1.11.1-next.0", ++ "@backstage/core-components": "^0.13.8-next.1", ++ "@backstage/core-plugin-api": "^1.8.0-next.0", ++ "@backstage/integration-react": "^1.1.21-next.1", ++ "@backstage/plugin-api-docs": "^0.9.13-next.1", ++ "@backstage/plugin-catalog": "^1.15.0-next.1", ++ "@backstage/plugin-catalog-common": "^1.0.17", ++ "@backstage/plugin-catalog-graph": "^0.2.38-next.1", ++ "@backstage/plugin-catalog-import": "^0.10.2-next.1", ++ "@backstage/plugin-catalog-react": "^1.9.0-next.1", ++ "@backstage/plugin-github-actions": "^0.6.7-next.1", ++ "@backstage/plugin-org": "^0.6.16-next.1", ++ "@backstage/plugin-permission-react": "^0.4.17-next.0", ++ "@backstage/plugin-scaffolder": "^1.16.0-next.1", ++ "@backstage/plugin-search": "^1.4.2-next.1", ++ "@backstage/plugin-search-react": "^1.7.2-next.1", ++ "@backstage/plugin-tech-radar": "^0.6.10-next.1", ++ "@backstage/plugin-techdocs": "^1.8.1-next.1", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.2-next.1", ++ "@backstage/plugin-techdocs-react": "^1.1.13-next.1", ++ "@backstage/plugin-user-settings": "^0.7.12-next.1", ++ "@backstage/theme": "^0.4.4-next.0", + "@material-ui/core": "^4.12.2", +@@ -28,31 +44,17 @@ + "history": "^5.0.0", +- "react": "^16.13.1", +- "react-dom": "^16.13.1", +- "react-router": "6.0.0-beta.0", +- "react-router-dom": "6.0.0-beta.0", +- "react-use": "^15.3.3" ++ "react": "^17.0.2", ++ "react-dom": "^17.0.2", ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^0.2.1", ++ "@backstage/test-utils": "^1.4.5-next.0", ++ "@playwright/test": "^1.32.3", + "@testing-library/jest-dom": "^5.10.1", +- "@testing-library/react": "^10.4.1", +- "@testing-library/user-event": "^12.0.7", +- "@types/jest": "^26.0.7", +- "@types/node": "^14.14.32", ++ "@testing-library/react": "^12.1.3", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^8.0.0", + "@types/react-dom": "*", +- "cross-env": "^7.0.0", +- "cypress": "^7.3.0", +- "eslint-plugin-cypress": "^2.10.3", +- "start-server-and-test": "^1.10.11" +- }, +- "scripts": { +- "start": "backstage-cli app:serve", +- "build": "backstage-cli app:build", +- "clean": "backstage-cli clean", +- "test": "backstage-cli test", +- "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", +- "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", +- "lint": "backstage-cli lint", +- "cy:dev": "cypress open", +- "cy:run": "cypress run" ++ "cross-env": "^7.0.0" + }, +diff --git a/packages/app/public/index.html b/packages/app/public/index.html +index a936c73..fc758ee 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -10,3 +10,2 @@ + /> +- + +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 8a53583..8d62f29 100644 +--- a/packages/app/src/App.tsx ++++ b/packages/app/src/App.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { Navigate, Route } from 'react-router'; ++import { Navigate, Route } from 'react-router-dom'; + import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; +@@ -13,2 +13,3 @@ import { + import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; ++import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +@@ -16,3 +17,2 @@ import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -21,2 +21,4 @@ import { + } from '@backstage/plugin-techdocs'; ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; + import { UserSettingsPage } from '@backstage/plugin-user-settings'; +@@ -29,3 +31,6 @@ import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; + import { createApp } from '@backstage/app-defaults'; +-import { FlatRoutes } from '@backstage/core-app-api'; ++import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; ++import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; ++import { RequirePermission } from '@backstage/plugin-permission-react'; ++import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; + +@@ -37,5 +42,6 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); + bind(apiDocsPlugin.externalRoutes, { +- createComponent: scaffolderPlugin.routes.root, ++ registerApi: catalogImportPlugin.routes.importPage, + }); +@@ -43,2 +49,6 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, ++ }); ++ bind(orgPlugin.externalRoutes, { ++ catalogIndex: catalogPlugin.routes.catalogIndex, + }); +@@ -47,8 +57,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -60,5 +67,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -73,3 +82,10 @@ const routes = ( + /> +- } /> ++ ++ ++ ++ } ++ /> + }> +@@ -78,2 +94,3 @@ const routes = ( + } /> ++ } /> + +@@ -81,4 +98,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -88,5 +105,3 @@ const App = () => ( + +- ++ , + ); +- +-export default App; +diff --git a/packages/app/src/components/Root/LogoFull.tsx b/packages/app/src/components/Root/LogoFull.tsx +index c7b1c84..47e3b73 100644 +--- a/packages/app/src/components/Root/LogoFull.tsx ++++ b/packages/app/src/components/Root/LogoFull.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/LogoIcon.tsx b/packages/app/src/components/Root/LogoIcon.tsx +index 073cf6e..7eae8c7 100644 +--- a/packages/app/src/components/Root/LogoIcon.tsx ++++ b/packages/app/src/components/Root/LogoIcon.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx +index b4fa04f..6768b48 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,19 +1,3 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- +-import React, { useContext, PropsWithChildren } from 'react'; +-import { Link, makeStyles } from '@material-ui/core'; ++import React, { PropsWithChildren } from 'react'; ++import { makeStyles } from '@material-ui/core'; + import HomeIcon from '@material-ui/icons/Home'; +@@ -25,18 +9,21 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; +-import { Settings as SidebarSettings } from '@backstage/plugin-user-settings'; + import { +- SidebarSearchModal, +- SearchContextProvider, +-} from '@backstage/plugin-search'; ++ Settings as SidebarSettings, ++ UserSettingsSignInAvatar, ++} from '@backstage/plugin-user-settings'; ++import { SidebarSearchModal } from '@backstage/plugin-search'; + import { + Sidebar, +- SidebarPage, + sidebarConfig, +- SidebarContext, +- SidebarItem, + SidebarDivider, +- SidebarSpace, ++ SidebarGroup, ++ SidebarItem, ++ SidebarPage, + SidebarScrollWrapper, ++ SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; ++import MenuIcon from '@material-ui/icons/Menu'; ++import SearchIcon from '@material-ui/icons/Search'; + +@@ -59,3 +46,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -63,8 +50,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -79,19 +61,27 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ } to="/search"> + +- ++ + +- {/* Global nav, not org-specific */} +- +- +- +- +- {/* End global nav */} +- +- +- +- ++ }> ++ {/* Global nav, not org-specific */} ++ ++ ++ ++ ++ {/* End global nav */} ++ ++ ++ ++ ++ + + +- ++ } ++ to="/settings" ++ > ++ ++ + +diff --git a/packages/app/src/components/Root/index.ts b/packages/app/src/components/Root/index.ts +index dff706f..3528881 100644 +--- a/packages/app/src/components/Root/index.ts ++++ b/packages/app/src/components/Root/index.ts +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + export { Root } from './Root'; +diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx +index d3b4b78..6722ea2 100644 +--- a/packages/app/src/components/catalog/EntityPage.tsx ++++ b/packages/app/src/components/catalog/EntityPage.tsx +@@ -1,16 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ + import React from 'react'; +@@ -29,3 +14,2 @@ import { + EntityDependsOnResourcesCard, +- EntitySystemDiagramCard, + EntityHasComponentsCard, +@@ -43,2 +27,4 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +@@ -56,2 +42,27 @@ import { EntityTechdocsContent } from '@backstage/plugin-techdocs'; + import { EmptyState } from '@backstage/core-components'; ++import { ++ Direction, ++ EntityCatalogGraphCard, ++} from '@backstage/plugin-catalog-graph'; ++import { ++ RELATION_API_CONSUMED_BY, ++ RELATION_API_PROVIDED_BY, ++ RELATION_CONSUMES_API, ++ RELATION_DEPENDENCY_OF, ++ RELATION_DEPENDS_ON, ++ RELATION_HAS_PART, ++ RELATION_PART_OF, ++ RELATION_PROVIDES_API, ++} from '@backstage/catalog-model'; ++ ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); + +@@ -94,2 +105,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -110,2 +129,6 @@ const overviewContent = ( + ++ ++ ++ ++ + +@@ -152,3 +175,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -179,3 +202,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -198,3 +221,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -225,2 +248,5 @@ const apiPage = ( + ++ ++ ++ + +@@ -292,3 +318,9 @@ const systemPage = ( + +- ++ ++ ++ ++ ++ ++ ++ + +@@ -304,3 +336,19 @@ const systemPage = ( + +- ++ + +@@ -317,2 +365,5 @@ const domainPage = ( + ++ ++ ++ + +diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx +index cf380b6..1788dde 100644 +--- a/packages/app/src/components/search/SearchPage.tsx ++++ b/packages/app/src/components/search/SearchPage.tsx +@@ -1,7 +1,12 @@ + import React from 'react'; +-import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; ++import { makeStyles, Theme, Grid, Paper } from '@material-ui/core'; + +-import { CatalogResultListItem } from '@backstage/plugin-catalog'; +-import { DocsResultListItem } from '@backstage/plugin-techdocs'; ++import { CatalogSearchResultListItem } from '@backstage/plugin-catalog'; ++import { ++ catalogApiRef, ++ CATALOG_FILTER_EXISTS, ++} from '@backstage/plugin-catalog-react'; ++import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; + ++import { SearchType } from '@backstage/plugin-search'; + import { +@@ -10,5 +15,5 @@ import { + SearchResult, +- SearchType, +- DefaultResultListItem, +-} from '@backstage/plugin-search'; ++ SearchPagination, ++ useSearch, ++} from '@backstage/plugin-search-react'; + import { +@@ -20,2 +25,3 @@ import { + } from '@backstage/core-components'; ++import { useApi } from '@backstage/core-plugin-api'; + +@@ -38,2 +44,4 @@ const SearchPage = () => { + const classes = useStyles(); ++ const { types } = useSearch(); ++ const catalogApi = useApi(catalogApiRef); + +@@ -67,4 +75,26 @@ const SearchPage = () => { + ++ {types.includes('techdocs') && ( ++ { ++ // Return a list of entities which are documented. ++ const { items } = await catalogApi.getEntities({ ++ fields: ['metadata.name'], ++ filter: { ++ 'metadata.annotations.backstage.io/techdocs-ref': ++ CATALOG_FILTER_EXISTS, ++ }, ++ }); ++ ++ const names = items.map(entity => entity.metadata.name); ++ names.sort(); ++ return names; ++ }} ++ /> ++ )} + { + className={classes.filter} ++ label="Lifecycle" + name="lifecycle" +@@ -80,32 +111,6 @@ const SearchPage = () => { + ++ + +- {({ results }) => ( +- +- {results.map(({ type, document }) => { +- switch (type) { +- case 'software-catalog': +- return ( +- +- ); +- case 'techdocs': +- return ( +- +- ); +- default: +- return ( +- +- ); +- } +- })} +- +- )} ++ } /> ++ } /> + +diff --git a/packages/backend/.eslintrc.js b/packages/backend/.eslintrc.js +index 16a033d..e2a53a6 100644 +--- a/packages/backend/.eslintrc.js ++++ b/packages/backend/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint.backend')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index 31231a3..18548e9 100644 +--- a/packages/backend/Dockerfile ++++ b/packages/backend/Dockerfile +@@ -7,3 +7,3 @@ + # yarn tsc +-# yarn build ++# yarn build:backend + # +@@ -11,6 +11,29 @@ + +-FROM node:14-buster-slim ++FROM node:18-bookworm-slim + ++# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends python3 g++ build-essential && \ ++ yarn config set python /usr/bin/python3 ++ ++# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, ++# in which case you should also move better-sqlite3 to "devDependencies" in package.json. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends libsqlite3-dev ++ ++# From here on we use the least-privileged `node` user to run the backend. ++USER node ++ ++# This should create the app dir as `node`. ++# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. ++# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. + WORKDIR /app + ++# This switches many Node.js dependencies to production mode. ++ENV NODE_ENV production ++ + # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. +@@ -18,11 +41,12 @@ WORKDIR /app + # and along with yarn.lock and the root package.json, that's enough to run yarn install. +-COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ ++COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ + RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz + +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + + # Then copy the rest of the backend bundle, along with any other files we might want. +-COPY packages/backend/dist/bundle.tar.gz app-config.yaml ./ ++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"] +diff --git a/packages/backend/README.md b/packages/backend/README.md +index 02426ef..3607b0a 100644 +--- a/packages/backend/README.md ++++ b/packages/backend/README.md +@@ -18,4 +18,2 @@ To run the example backend, first go to the project root and run + yarn install +-yarn tsc +-yarn build + ``` +@@ -27,7 +25,2 @@ After that, go to the `packages/backend` directory and run + ```bash +-AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x \ +-AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x \ +-AUTH_OAUTH2_CLIENT_ID=x AUTH_OAUTH2_CLIENT_SECRET=x \ +-AUTH_OAUTH2_AUTH_URL=x AUTH_OAUTH2_TOKEN_URL=x \ +-LOG_LEVEL=debug \ + yarn start +@@ -35,4 +28,4 @@ yarn start + +-Substitute `x` for actual values, or leave them as dummy values just to try out +-the backend without using the auth or sentry features. ++If you want to override any configuration locally, for example adding any secrets, ++you can do so in `app-config.local.yaml`. + +@@ -45,3 +38,3 @@ locations to the backend. These are places where the backend can find some + entity descriptor data to consume and serve. For more information, see +-[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog). ++[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). + +@@ -65,2 +58,2 @@ and + - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +-- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) ++- [Backstage Documentation](https://backstage.io/docs) +diff --git a/packages/backend/package.json b/packages/backend/package.json +index afdde8d..f224eaf 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,28 +6,33 @@ + "private": true, ++ "backstage": { ++ "role": "backend" ++ }, + "scripts": { +- "build": "backstage-cli backend:bundle", +- "build-image": "docker build ../.. -f Dockerfile --tag backstage", +- "start": "backstage-cli backend:dev", +- "lint": "backstage-cli lint", +- "test": "backstage-cli test", +- "clean": "backstage-cli clean", +- "migrate:create": "knex migrate:make -x ts" ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "lint": "backstage-cli package lint", ++ "test": "backstage-cli package test", ++ "clean": "backstage-cli package clean", ++ "build-image": "docker build ../.. -f Dockerfile --tag backstage" + }, + "dependencies": { +- "app": "0.0.0", +- "@backstage/backend-common": "^0.10.1", +- "@backstage/backend-tasks": "^0.1.1", +- "@backstage/catalog-model": "^0.9.8", +- "@backstage/catalog-client": "^0.5.3", +- "@backstage/config": "^0.1.11", +- "@backstage/plugin-app-backend": "^0.3.21", +- "@backstage/plugin-auth-backend": "^0.6.0", +- "@backstage/plugin-catalog-backend": "^0.19.4", +- "@backstage/plugin-proxy-backend": "^0.2.15", +- "@backstage/plugin-scaffolder-backend": "^0.15.19", +- "@backstage/plugin-search-backend": "^0.3.0", +- "@backstage/plugin-search-backend-node": "^0.4.3", +- "@backstage/plugin-techdocs-backend": "^0.12.2", +- "@gitbeaker/node": "^34.6.0", +- "@octokit/rest": "^18.5.3", ++ "@backstage/backend-common": "^0.19.9-next.1", ++ "@backstage/backend-tasks": "^0.5.12-next.1", ++ "@backstage/catalog-client": "^1.4.5", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/config": "^1.1.1", ++ "@backstage/plugin-app-backend": "^0.3.55-next.1", ++ "@backstage/plugin-auth-backend": "^0.20.0-next.1", ++ "@backstage/plugin-auth-node": "^0.4.1-next.1", ++ "@backstage/plugin-catalog-backend": "^1.15.0-next.1", ++ "@backstage/plugin-permission-common": "^0.7.9", ++ "@backstage/plugin-permission-node": "^0.7.18-next.1", ++ "@backstage/plugin-proxy-backend": "^0.4.5-next.1", ++ "@backstage/plugin-scaffolder-backend": "^1.19.0-next.1", ++ "@backstage/plugin-search-backend": "^1.4.7-next.1", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.16-next.1", ++ "@backstage/plugin-search-backend-node": "^1.2.11-next.1", ++ "@backstage/plugin-techdocs-backend": "^1.9.0-next.1", ++ "app": "link:../app", ++ "better-sqlite3": "^8.0.0", + "dockerode": "^3.3.1", +@@ -35,4 +40,3 @@ + "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "pg": "^8.3.0", + "winston": "^3.2.1" +@@ -40,6 +44,7 @@ + "devDependencies": { +- "@backstage/cli": "^0.10.4", ++ "@backstage/cli": "^0.24.0-next.1", + "@types/dockerode": "^3.3.0", + "@types/express": "^4.17.6", +- "@types/express-serve-static-core": "^4.17.5" ++ "@types/express-serve-static-core": "^4.17.5", ++ "@types/luxon": "^2.0.4" + }, +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 08d21e6..04c4ff9 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -17,3 +17,3 @@ import { + DatabaseManager, +- SingleHostDiscovery, ++ HostDiscovery, + UrlReaders, +@@ -31,2 +31,4 @@ import search from './plugins/search'; + import { PluginEnvironment } from './types'; ++import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; + +@@ -35,7 +37,15 @@ function makeCreateEnv(config: Config) { + const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++ const discovery = HostDiscovery.fromConfig(config); + const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++ const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); + const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); ++ const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); ++ ++ const identity = DefaultIdentityClient.create({ ++ discovery, ++ }); ++ const permissions = ServerPermissionClient.fromConfig(config, { ++ discovery, ++ tokenManager, ++ }); + +@@ -57,2 +67,4 @@ function makeCreateEnv(config: Config) { + scheduler, ++ permissions, ++ identity, + }; +@@ -100,3 +112,3 @@ module.hot?.accept(); + main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); ++ console.error('Backend failed to start up', error); + process.exit(1); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +index 07fb04f..7c37f68 100644 +--- a/packages/backend/src/plugins/app.ts ++++ b/packages/backend/src/plugins/app.ts +@@ -4,9 +4,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + return await createRouter({ +- logger, +- config, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, + appPackageName: 'app', +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +index 5216510..77eb6aa 100644 +--- a/packages/backend/src/plugins/auth.ts ++++ b/packages/backend/src/plugins/auth.ts +@@ -1,2 +1,6 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; ++import { ++ createRouter, ++ providers, ++ defaultAuthProviderFactories, ++} from '@backstage/plugin-auth-backend'; + import { Router } from 'express'; +@@ -4,9 +8,47 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, database, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, ++ providerFactories: { ++ ...defaultAuthProviderFactories, ++ ++ // This replaces the default GitHub auth provider with a customized one. ++ // The `signIn` option enables sign-in for this provider, using the ++ // identity resolution logic that's provided in the `resolver` callback. ++ // ++ // This particular resolver makes all users share a single "guest" identity. ++ // It should only be used for testing and trying out Backstage. ++ // ++ // If you want to use a production ready resolver you can switch to ++ // the one that is commented out below, it looks up a user entity in the ++ // catalog using the GitHub username of the authenticated user. ++ // That resolver requires you to have user entities populated in the catalog, ++ // for example using https://backstage.io/docs/integrations/github/org ++ // ++ // There are other resolvers to choose from, and you can also create ++ // your own, see the auth documentation for more details: ++ // ++ // https://backstage.io/docs/auth/identity-resolver ++ github: providers.github.create({ ++ signIn: { ++ resolver(_, ctx) { ++ const userRef = 'user:default/guest'; // Must be a full entity reference ++ return ctx.issueToken({ ++ claims: { ++ sub: userRef, // The user's own identity ++ ent: [userRef], // A list of identities that the user claims ownership through ++ }, ++ }); ++ }, ++ // resolver: providers.github.resolvers.usernameMatchingUserEntityName(), ++ }, ++ }), ++ }, ++ }); + } +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +index 506f6d9..54ec393 100644 +--- a/packages/backend/src/plugins/proxy.ts ++++ b/packages/backend/src/plugins/proxy.ts +@@ -4,8 +4,10 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ }); + } +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +index 6be2e97..a12fee2 100644 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ b/packages/backend/src/plugins/scaffolder.ts +@@ -1,5 +1,3 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; + import { CatalogClient } from '@backstage/catalog-client'; + import { createRouter } from '@backstage/plugin-scaffolder-backend'; +-import Docker from 'dockerode'; + import { Router } from 'express'; +@@ -7,20 +5,17 @@ import type { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- database, +- reader, +- discovery, +-}: PluginEnvironment): Promise { +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- const catalogClient = new CatalogClient({ discoveryApi: discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ const catalogClient = new CatalogClient({ ++ discoveryApi: env.discovery, ++ }); + + return await createRouter({ +- containerRunner, +- logger, +- config, +- database, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ reader: env.reader, + catalogClient, +- reader, ++ identity: env.identity, ++ permissions: env.permissions, + }); +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +index f23b0c7..e9469dc 100644 +--- a/packages/backend/src/plugins/search.ts ++++ b/packages/backend/src/plugins/search.ts +@@ -7,14 +7,25 @@ import { + import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend'; ++import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend'; ++import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; ++import { Router } from 'express'; + +-export default async function createPlugin({ +- logger, +- discovery, +- config, +- tokenManager, +-}: PluginEnvironment) { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); ++ const searchEngine = new LunrSearchEngine({ ++ logger: env.logger, ++ }); ++ const indexBuilder = new IndexBuilder({ ++ logger: env.logger, ++ searchEngine, ++ }); ++ ++ const schedule = env.scheduler.createScheduledTaskRunner({ ++ frequency: { minutes: 10 }, ++ timeout: { minutes: 15 }, ++ // A 3 second delay gives the backend server a chance to initialize before ++ // any collators are executed, which may attempt requests against the API. ++ initialDelay: { seconds: 3 }, ++ }); + +@@ -23,6 +34,6 @@ export default async function createPlugin({ + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { +- discovery, +- tokenManager, ++ schedule, ++ factory: DefaultCatalogCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, + }), +@@ -32,7 +43,7 @@ export default async function createPlugin({ + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultTechDocsCollator.fromConfig(config, { +- discovery, +- logger, +- tokenManager, ++ schedule, ++ factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ logger: env.logger, ++ tokenManager: env.tokenManager, + }), +@@ -43,6 +54,4 @@ export default async function createPlugin({ + const { scheduler } = await indexBuilder.build(); ++ scheduler.start(); + +- // A 3 second delay gives the backend server a chance to initialize before +- // any collators are executed, which may attempt requests against the API. +- setTimeout(() => scheduler.start(), 3000); + useHotCleanup(module, () => scheduler.stop()); +@@ -51,3 +60,6 @@ export default async function createPlugin({ + engine: indexBuilder.getSearchEngine(), +- logger, ++ types: indexBuilder.getDocumentTypes(), ++ permissions: env.permissions, ++ config: env.config, ++ logger: env.logger, + }); +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +index 054c64d..be8bb0c 100644 +--- a/packages/backend/src/plugins/techdocs.ts ++++ b/packages/backend/src/plugins/techdocs.ts +@@ -11,13 +11,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +- cache, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, ++ const preparers = await Preparers.fromConfig(env.config, { ++ logger: env.logger, ++ reader: env.reader, + }); +@@ -29,4 +25,4 @@ export default async function createPlugin({ + // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, ++ const generators = await Generators.fromConfig(env.config, { ++ logger: env.logger, + containerRunner, +@@ -37,5 +33,5 @@ export default async function createPlugin({ + // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, ++ const publisher = await Publisher.fromConfig(env.config, { ++ logger: env.logger, ++ discovery: env.discovery, + }); +@@ -49,6 +45,6 @@ export default async function createPlugin({ + publisher, +- logger, +- config, +- discovery, +- cache, ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ cache: env.cache, + }); +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +index c3d0158..9cd2c74 100644 +--- a/packages/backend/src/types.ts ++++ b/packages/backend/src/types.ts +@@ -10,2 +10,4 @@ import { + import { PluginTaskScheduler } from '@backstage/backend-tasks'; ++import { PermissionEvaluator } from '@backstage/plugin-permission-common'; ++import { IdentityApi } from '@backstage/plugin-auth-node'; + +@@ -20,2 +22,4 @@ export type PluginEnvironment = { + scheduler: PluginTaskScheduler; ++ permissions: PermissionEvaluator; ++ identity: IdentityApi; + }; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..37c7fb1 +--- /dev/null ++++ b/playwright.config.ts +@@ -0,0 +1,60 @@ ++/* ++ * Copyright 2023 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { defineConfig } from '@playwright/test'; ++import { generateProjects } from '@backstage/e2e-test-utils/playwright'; ++ ++/** ++ * See https://playwright.dev/docs/test-configuration. ++ */ ++export default defineConfig({ ++ timeout: 60_000, ++ ++ expect: { ++ timeout: 5_000, ++ }, ++ ++ // Run your local dev server before starting the tests ++ webServer: process.env.CI ++ ? [] ++ : [ ++ { ++ command: 'yarn start', ++ port: 3000, ++ reuseExistingServer: true, ++ timeout: 60_000, ++ }, ++ ], ++ ++ forbidOnly: !!process.env.CI, ++ ++ retries: process.env.CI ? 2 : 0, ++ ++ reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]], ++ ++ use: { ++ actionTimeout: 0, ++ baseURL: ++ process.env.PLAYWRIGHT_URL ?? ++ (process.env.CI ? 'http://localhost:7007' : 'http://localhost:3000'), ++ screenshot: 'only-on-failure', ++ trace: 'on-first-retry', ++ }, ++ ++ outputDir: 'node_modules/.cache/e2e-test-results', ++ ++ projects: generateProjects(), // Find all packages with e2e-test folders ++}); +diff --git a/plugins/README.md b/plugins/README.md +new file mode 100644 +index 0000000..d7865fd +--- /dev/null ++++ b/plugins/README.md +@@ -0,0 +1,9 @@ ++# The Plugins Folder ++ ++This is where your own plugins and their associated modules live, each in a ++separate folder of its own. ++ ++If you want to create a new plugin here, go to your project root directory, run ++the command `yarn new`, and follow the on-screen instructions. ++ ++You can also check out existing plugins on [the plugin marketplace](https://backstage.io/plugins)! +diff --git a/yarn.lock b/yarn.lock +new file mode 100644 +index 0000000..5ad7fe2 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1 @@ ++# intentionally left empty diff --git a/diffs/0.4.11..0.5.7-next.1.diff b/diffs/0.4.11..0.5.7-next.1.diff new file mode 100644 index 00000000..05f8b387 --- /dev/null +++ b/diffs/0.4.11..0.5.7-next.1.diff @@ -0,0 +1,1833 @@ +diff --git a/.dockerignore b/.dockerignore +index 63c9c34..05edb62 100644 +--- a/.dockerignore ++++ b/.dockerignore +@@ -1,5 +1,8 @@ + .git ++.yarn/cache ++.yarn/install-state.gz + node_modules +-packages +-!packages/backend/dist ++packages/*/src ++packages/*/node_modules + plugins ++*.local.yaml +diff --git a/.eslintignore b/.eslintignore +new file mode 100644 +index 0000000..e5b1994 +--- /dev/null ++++ b/.eslintignore +@@ -0,0 +1 @@ ++playwright.config.ts +diff --git a/.gitignore b/.gitignore +index d16a8d3..fbf8139 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -17,2 +17,11 @@ node_modules/ + ++# Yarn 3 files ++.pnp.* ++.yarn/* ++!.yarn/patches ++!.yarn/plugins ++!.yarn/releases ++!.yarn/sdks ++!.yarn/versions ++ + # Node version directives +@@ -39 +48,7 @@ site + *-credentials.yaml ++ ++# vscode database functionality support files ++*.session.sql ++ ++# E2E test reports ++e2e-test-report/ +diff --git a/app-config.production.yaml b/app-config.production.yaml +index 5e36c23..8f0751c 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -1,3 +1,3 @@ + app: +- # Should be the same as backend.baseUrl when using the `app-backend` plugin ++ # Should be the same as backend.baseUrl when using the `app-backend` plugin. + baseUrl: http://localhost:7007 +@@ -5,4 +5,31 @@ app: + backend: ++ # Note that the baseUrl should be the URL that the browser and other clients ++ # should use when communicating with the backend, i.e. it needs to be ++ # reachable not just from within the backend host, but from all of your ++ # callers. When its value is "http://localhost:7007", it's strictly private ++ # and can't be reached by others. + baseUrl: http://localhost:7007 +- listen: +- port: 7007 ++ # The listener can also be expressed as a single : string. In this case we bind to ++ # all interfaces, the most permissive setting. The right value depends on your specific deployment. ++ listen: ':7007' ++ ++ # config options: https://node-postgres.com/api/client ++ database: ++ client: pg ++ connection: ++ host: ${POSTGRES_HOST} ++ port: ${POSTGRES_PORT} ++ user: ${POSTGRES_USER} ++ password: ${POSTGRES_PASSWORD} ++ # https://node-postgres.com/features/ssl ++ # you can set the sslmode configuration option via the `PGSSLMODE` environment variable ++ # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require) ++ # ssl: ++ # ca: # if you have a CA file and want to verify it you can uncomment this section ++ # $file: /ca/server.crt ++ ++catalog: ++ # Overrides the default list locations from app-config.yaml as these contain example data. ++ # See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details ++ # on how to get entities into the catalog. ++ locations: [] +diff --git a/app-config.yaml b/app-config.yaml +index 1a622a2..08d2abf 100644 +--- a/app-config.yaml ++++ b/app-config.yaml +@@ -9,3 +9,4 @@ backend: + # Used for enabling authentication, secret is shared by all backend plugins +- # See backend-to-backend-auth.md in the docs for information on the format ++ # See https://backstage.io/docs/auth/service-to-service-auth for ++ # information on the format + # auth: +@@ -16,2 +17,4 @@ backend: + port: 7007 ++ # Uncomment the following host directive to bind to specific interfaces ++ # host: 127.0.0.1 + csp: +@@ -22,9 +25,9 @@ backend: + origin: http://localhost:3000 +- methods: [GET, POST, PUT, DELETE] ++ methods: [GET, HEAD, PATCH, POST, PUT, DELETE] + credentials: true ++ # This is for local development only, it is not recommended to use this in production ++ # The production database configuration is stored in app-config.production.yaml + database: +- client: sqlite3 ++ client: better-sqlite3 + connection: ':memory:' +- cache: +- store: memory + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir +@@ -34,2 +37,4 @@ integrations: + - host: github.com ++ # This is a Personal Access Token or PAT from GitHub. You can find out how to generate this token, and more information ++ # about setting up the GitHub integration here: https://backstage.io/docs/getting-started/configuration#setting-up-a-github-integration + token: ${GITHUB_TOKEN} +@@ -41,5 +46,8 @@ integrations: + proxy: +- '/test': +- target: 'https://example.com' +- changeOrigin: true ++ ### Example for how to add a proxy endpoint for the frontend. ++ ### A typical reason to do this is to handle HTTPS and CORS for internal services. ++ # endpoints: ++ # '/test': ++ # target: 'https://example.com' ++ # changeOrigin: true + +@@ -64,41 +72,32 @@ scaffolder: + catalog: ++ import: ++ entityFilename: catalog-info.yaml ++ pullRequestBranchName: backstage-integration + rules: +- - allow: [Component, System, API, Group, User, Resource, Location] ++ - allow: [Component, System, API, Resource, Location] + locations: +- # Backstage example components +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-components.yaml ++ # Local example data, file locations are relative to the backend process, typically `packages/backend` ++ - type: file ++ target: ../../examples/entities.yaml + +- # Backstage example systems +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-systems.yaml +- +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml +- +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml +- +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.yaml +- +- # Backstage example templates +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/react-ssr-template/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/springboot-grpc-template/template.yaml ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml + rules: + - allow: [Template] +- - type: url +- target: https://github.com/spotify/cookiecutter-golang/blob/master/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/docs-template/template.yaml ++ ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml + rules: +- - allow: [Template] ++ - allow: [User, Group] ++ ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml ++ ++ ## Uncomment these lines to add an example org ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme-corp.yaml ++ # rules: ++ # - allow: [User, Group] +diff --git a/backstage.json b/backstage.json +index 35313e0..707af29 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "0.4.11" ++ "version": "1.20.0-next.1" + } +diff --git a/examples/entities.yaml b/examples/entities.yaml +new file mode 100644 +index 0000000..447e8b1 +--- /dev/null ++++ b/examples/entities.yaml +@@ -0,0 +1,41 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system ++apiVersion: backstage.io/v1alpha1 ++kind: System ++metadata: ++ name: examples ++spec: ++ owner: guests ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: example-website ++spec: ++ type: website ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ providesApis: [example-grpc-api] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api ++apiVersion: backstage.io/v1alpha1 ++kind: API ++metadata: ++ name: example-grpc-api ++spec: ++ type: grpc ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ definition: | ++ syntax = "proto3"; ++ ++ service Exampler { ++ rpc Example (ExampleMessage) returns (ExampleMessage) {}; ++ } ++ ++ message ExampleMessage { ++ string example = 1; ++ }; +diff --git a/examples/org.yaml b/examples/org.yaml +new file mode 100644 +index 0000000..a10e81f +--- /dev/null ++++ b/examples/org.yaml +@@ -0,0 +1,17 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user ++apiVersion: backstage.io/v1alpha1 ++kind: User ++metadata: ++ name: guest ++spec: ++ memberOf: [guests] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group ++apiVersion: backstage.io/v1alpha1 ++kind: Group ++metadata: ++ name: guests ++spec: ++ type: team ++ children: [] +diff --git a/examples/template/content/catalog-info.yaml b/examples/template/content/catalog-info.yaml +new file mode 100644 +index 0000000..d4ccca4 +--- /dev/null ++++ b/examples/template/content/catalog-info.yaml +@@ -0,0 +1,8 @@ ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: ${{ values.name | dump }} ++spec: ++ type: service ++ owner: user:guest ++ lifecycle: experimental +diff --git a/examples/template/content/index.js b/examples/template/content/index.js +new file mode 100644 +index 0000000..071ce5a +--- /dev/null ++++ b/examples/template/content/index.js +@@ -0,0 +1 @@ ++console.log('Hello from ${{ values.name }}!'); +diff --git a/examples/template/content/package.json b/examples/template/content/package.json +new file mode 100644 +index 0000000..86f968a +--- /dev/null ++++ b/examples/template/content/package.json +@@ -0,0 +1,5 @@ ++{ ++ "name": "${{ values.name }}", ++ "private": true, ++ "dependencies": {} ++} +diff --git a/examples/template/template.yaml b/examples/template/template.yaml +new file mode 100644 +index 0000000..33f262b +--- /dev/null ++++ b/examples/template/template.yaml +@@ -0,0 +1,74 @@ ++apiVersion: scaffolder.backstage.io/v1beta3 ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template ++kind: Template ++metadata: ++ name: example-nodejs-template ++ title: Example Node.js Template ++ description: An example template for the scaffolder that creates a simple Node.js service ++spec: ++ owner: user:guest ++ type: service ++ ++ # These parameters are used to generate the input form in the frontend, and are ++ # used to gather input data for the execution of the template. ++ parameters: ++ - title: Fill in some steps ++ required: ++ - name ++ properties: ++ name: ++ title: Name ++ type: string ++ description: Unique name of the component ++ ui:autofocus: true ++ ui:options: ++ rows: 5 ++ - title: Choose a location ++ required: ++ - repoUrl ++ properties: ++ repoUrl: ++ title: Repository Location ++ type: string ++ ui:field: RepoUrlPicker ++ ui:options: ++ allowedHosts: ++ - github.com ++ ++ # These steps are executed in the scaffolder backend, using data that we gathered ++ # via the parameters above. ++ steps: ++ # Each step executes an action, in this case one templates files into the working directory. ++ - id: fetch-base ++ name: Fetch Base ++ action: fetch:template ++ input: ++ url: ./content ++ values: ++ name: ${{ parameters.name }} ++ ++ # This step publishes the contents of the working directory to GitHub. ++ - id: publish ++ name: Publish ++ action: publish:github ++ input: ++ allowedHosts: ['github.com'] ++ description: This is ${{ parameters.name }} ++ repoUrl: ${{ parameters.repoUrl }} ++ ++ # The final step is to register our new component in the catalog. ++ - id: register ++ name: Register ++ action: catalog:register ++ input: ++ repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} ++ catalogInfoPath: '/catalog-info.yaml' ++ ++ # Outputs are displayed to the user after a successful execution of the template. ++ output: ++ links: ++ - title: Repository ++ url: ${{ steps['publish'].output.remoteUrl }} ++ - title: Open in catalog ++ icon: catalog ++ entityRef: ${{ steps['register'].output.entityRef }} +diff --git a/lerna.json b/lerna.json +index 322929d..529a62f 100644 +--- a/lerna.json ++++ b/lerna.json +@@ -3,4 +3,4 @@ + "npmClient": "yarn", +- "useWorkspaces": true, +- "version": "0.1.0" ++ "version": "0.1.0", ++ "$schema": "node_modules/lerna/schemas/lerna-schema.json" + } +diff --git a/package.json b/package.json +index eeca538..2364da8 100644 +--- a/package.json ++++ b/package.json +@@ -5,3 +5,3 @@ + "engines": { +- "node": "14 || 16" ++ "node": "18 || 20" + }, +@@ -11,3 +11,4 @@ + "start-backend": "yarn workspace backend start", +- "build": "lerna run build", ++ "build:backend": "yarn workspace backend build", ++ "build:all": "backstage-cli repo build --all", + "build-image": "yarn workspace backend build-image", +@@ -15,11 +16,11 @@ + "tsc:full": "tsc --skipLibCheck false --incremental false", +- "clean": "backstage-cli clean && lerna run clean", +- "diff": "lerna run diff --", +- "test": "backstage-cli test", +- "test:all": "lerna run test -- --coverage", +- "lint": "lerna run lint --since origin/master --", +- "lint:all": "lerna run lint --", ++ "clean": "backstage-cli repo clean", ++ "test": "backstage-cli repo test", ++ "test:all": "backstage-cli repo test --coverage", ++ "test:e2e": "playwright test", ++ "fix": "backstage-cli repo fix", ++ "lint": "backstage-cli repo lint --since origin/master", ++ "lint:all": "backstage-cli repo lint", + "prettier:check": "prettier --check .", +- "create-plugin": "backstage-cli create-plugin --scope internal", +- "remove-plugin": "backstage-cli remove-plugin" ++ "new": "backstage-cli new --scope internal" + }, +@@ -32,7 +33,15 @@ + "devDependencies": { +- "@backstage/cli": "^0.10.5", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/e2e-test-utils": "^0.1.0", ++ "@playwright/test": "^1.32.3", + "@spotify/prettier-config": "^12.0.0", +- "concurrently": "^6.0.0", +- "lerna": "^4.0.0", +- "prettier": "^2.3.2" ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^9.0.0", ++ "prettier": "^2.3.2", ++ "typescript": "~5.2.0" ++ }, ++ "resolutions": { ++ "@types/react": "^17", ++ "@types/react-dom": "^17" + }, +diff --git a/packages/README.md b/packages/README.md +new file mode 100644 +index 0000000..6327fa0 +--- /dev/null ++++ b/packages/README.md +@@ -0,0 +1,9 @@ ++# The Packages Folder ++ ++This is where your own applications and centrally managed libraries live, each ++in a separate folder of its own. ++ ++From the start there's an `app` folder (for the frontend) and a `backend` folder ++(for the Node backend), but you can also add more modules in here that house ++your core additions and adaptations, such as themes, common React component ++libraries, utilities, and similar. +diff --git a/packages/app/.eslintignore b/packages/app/.eslintignore +new file mode 100644 +index 0000000..a48cf0d +--- /dev/null ++++ b/packages/app/.eslintignore +@@ -0,0 +1 @@ ++public +diff --git a/packages/app/.eslintrc.js b/packages/app/.eslintrc.js +index 13573ef..e2a53a6 100644 +--- a/packages/app/.eslintrc.js ++++ b/packages/app/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/app/cypress.json b/packages/app/cypress.json +deleted file mode 100644 +index 5de7ebf..0000000 +--- a/packages/app/cypress.json ++++ /dev/null +@@ -1,5 +0,0 @@ +-{ +- "baseUrl": "http://localhost:3001", +- "fixturesFolder": false, +- "pluginsFile": false +-} +diff --git a/packages/app/cypress/.eslintrc.json b/packages/app/cypress/.eslintrc.json +deleted file mode 100644 +index 2b3a458..0000000 +--- a/packages/app/cypress/.eslintrc.json ++++ /dev/null +@@ -1,21 +0,0 @@ +-{ +- "plugins": ["cypress"], +- "extends": ["plugin:cypress/recommended"], +- "rules": { +- "jest/expect-expect": [ +- "error", +- { +- "assertFunctionNames": ["expect", "cy.contains"] +- } +- ], +- "import/no-extraneous-dependencies": [ +- "error", +- { +- "devDependencies": true, +- "optionalDependencies": true, +- "peerDependencies": true, +- "bundledDependencies": true +- } +- ] +- } +-} +diff --git a/packages/app/cypress/integration/app.js b/packages/app/cypress/integration/app.js +deleted file mode 100644 +index 43fb2e3..0000000 +--- a/packages/app/cypress/integration/app.js ++++ /dev/null +@@ -1,6 +0,0 @@ +-describe('App', () => { +- it('should render the catalog', () => { +- cy.visit('/'); +- cy.contains('My Company Catalog'); +- }); +-}); +diff --git a/packages/app/e2e-tests/app.test.ts b/packages/app/e2e-tests/app.test.ts +new file mode 100644 +index 0000000..d45bc0d +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,23 @@ ++/* ++ * Copyright 2020 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { test, expect } from '@playwright/test'; ++ ++test('App should render the welcome page', async ({ page }) => { ++ await page.goto('/'); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index a077d5f..2100405 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,22 +5,38 @@ + "bundled": true, ++ "backstage": { ++ "role": "frontend" ++ }, ++ "scripts": { ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "clean": "backstage-cli package clean", ++ "test": "backstage-cli package test", ++ "lint": "backstage-cli package lint" ++ }, + "dependencies": { +- "@backstage/app-defaults": "^0.1.3", +- "@backstage/catalog-model": "^0.9.8", +- "@backstage/cli": "^0.10.5", +- "@backstage/core-app-api": "^0.3.1", +- "@backstage/core-components": "^0.8.3", +- "@backstage/core-plugin-api": "^0.4.1", +- "@backstage/integration-react": "^0.1.17", +- "@backstage/plugin-api-docs": "^0.6.21", +- "@backstage/plugin-catalog": "^0.7.7", +- "@backstage/plugin-catalog-import": "^0.7.8", +- "@backstage/plugin-catalog-react": "^0.6.10", +- "@backstage/plugin-github-actions": "^0.4.30", +- "@backstage/plugin-org": "^0.3.33", +- "@backstage/plugin-scaffolder": "^0.11.17", +- "@backstage/plugin-search": "^0.5.4", +- "@backstage/plugin-tech-radar": "^0.5.1", +- "@backstage/plugin-techdocs": "^0.12.13", +- "@backstage/plugin-user-settings": "^0.3.15", +- "@backstage/theme": "^0.2.14", ++ "@backstage/app-defaults": "^1.4.5-next.1", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/core-app-api": "^1.11.1-next.0", ++ "@backstage/core-components": "^0.13.8-next.1", ++ "@backstage/core-plugin-api": "^1.8.0-next.0", ++ "@backstage/integration-react": "^1.1.21-next.1", ++ "@backstage/plugin-api-docs": "^0.9.13-next.1", ++ "@backstage/plugin-catalog": "^1.15.0-next.1", ++ "@backstage/plugin-catalog-common": "^1.0.17", ++ "@backstage/plugin-catalog-graph": "^0.2.38-next.1", ++ "@backstage/plugin-catalog-import": "^0.10.2-next.1", ++ "@backstage/plugin-catalog-react": "^1.9.0-next.1", ++ "@backstage/plugin-github-actions": "^0.6.7-next.1", ++ "@backstage/plugin-org": "^0.6.16-next.1", ++ "@backstage/plugin-permission-react": "^0.4.17-next.0", ++ "@backstage/plugin-scaffolder": "^1.16.0-next.1", ++ "@backstage/plugin-search": "^1.4.2-next.1", ++ "@backstage/plugin-search-react": "^1.7.2-next.1", ++ "@backstage/plugin-tech-radar": "^0.6.10-next.1", ++ "@backstage/plugin-techdocs": "^1.8.1-next.1", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.2-next.1", ++ "@backstage/plugin-techdocs-react": "^1.1.13-next.1", ++ "@backstage/plugin-user-settings": "^0.7.12-next.1", ++ "@backstage/theme": "^0.4.4-next.0", + "@material-ui/core": "^4.12.2", +@@ -28,31 +44,17 @@ + "history": "^5.0.0", +- "react": "^16.13.1", +- "react-dom": "^16.13.1", +- "react-router": "6.0.0-beta.0", +- "react-router-dom": "6.0.0-beta.0", +- "react-use": "^15.3.3" ++ "react": "^17.0.2", ++ "react-dom": "^17.0.2", ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^0.2.1", ++ "@backstage/test-utils": "^1.4.5-next.0", ++ "@playwright/test": "^1.32.3", + "@testing-library/jest-dom": "^5.10.1", +- "@testing-library/react": "^10.4.1", +- "@testing-library/user-event": "^12.0.7", +- "@types/jest": "^26.0.7", +- "@types/node": "^14.14.32", ++ "@testing-library/react": "^12.1.3", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^8.0.0", + "@types/react-dom": "*", +- "cross-env": "^7.0.0", +- "cypress": "^7.3.0", +- "eslint-plugin-cypress": "^2.10.3", +- "start-server-and-test": "^1.10.11" +- }, +- "scripts": { +- "start": "backstage-cli app:serve", +- "build": "backstage-cli app:build", +- "clean": "backstage-cli clean", +- "test": "backstage-cli test", +- "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", +- "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", +- "lint": "backstage-cli lint", +- "cy:dev": "cypress open", +- "cy:run": "cypress run" ++ "cross-env": "^7.0.0" + }, +diff --git a/packages/app/public/index.html b/packages/app/public/index.html +index a936c73..fc758ee 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -10,3 +10,2 @@ + /> +- + +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 8a53583..8d62f29 100644 +--- a/packages/app/src/App.tsx ++++ b/packages/app/src/App.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { Navigate, Route } from 'react-router'; ++import { Navigate, Route } from 'react-router-dom'; + import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; +@@ -13,2 +13,3 @@ import { + import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; ++import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +@@ -16,3 +17,2 @@ import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -21,2 +21,4 @@ import { + } from '@backstage/plugin-techdocs'; ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; + import { UserSettingsPage } from '@backstage/plugin-user-settings'; +@@ -29,3 +31,6 @@ import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; + import { createApp } from '@backstage/app-defaults'; +-import { FlatRoutes } from '@backstage/core-app-api'; ++import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; ++import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; ++import { RequirePermission } from '@backstage/plugin-permission-react'; ++import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; + +@@ -37,5 +42,6 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); + bind(apiDocsPlugin.externalRoutes, { +- createComponent: scaffolderPlugin.routes.root, ++ registerApi: catalogImportPlugin.routes.importPage, + }); +@@ -43,2 +49,6 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, ++ }); ++ bind(orgPlugin.externalRoutes, { ++ catalogIndex: catalogPlugin.routes.catalogIndex, + }); +@@ -47,8 +57,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -60,5 +67,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -73,3 +82,10 @@ const routes = ( + /> +- } /> ++ ++ ++ ++ } ++ /> + }> +@@ -78,2 +94,3 @@ const routes = ( + } /> ++ } /> + +@@ -81,4 +98,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -88,5 +105,3 @@ const App = () => ( + +- ++ , + ); +- +-export default App; +diff --git a/packages/app/src/components/Root/LogoFull.tsx b/packages/app/src/components/Root/LogoFull.tsx +index c7b1c84..47e3b73 100644 +--- a/packages/app/src/components/Root/LogoFull.tsx ++++ b/packages/app/src/components/Root/LogoFull.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/LogoIcon.tsx b/packages/app/src/components/Root/LogoIcon.tsx +index 073cf6e..7eae8c7 100644 +--- a/packages/app/src/components/Root/LogoIcon.tsx ++++ b/packages/app/src/components/Root/LogoIcon.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx +index b4fa04f..6768b48 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,19 +1,3 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- +-import React, { useContext, PropsWithChildren } from 'react'; +-import { Link, makeStyles } from '@material-ui/core'; ++import React, { PropsWithChildren } from 'react'; ++import { makeStyles } from '@material-ui/core'; + import HomeIcon from '@material-ui/icons/Home'; +@@ -25,18 +9,21 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; +-import { Settings as SidebarSettings } from '@backstage/plugin-user-settings'; + import { +- SidebarSearchModal, +- SearchContextProvider, +-} from '@backstage/plugin-search'; ++ Settings as SidebarSettings, ++ UserSettingsSignInAvatar, ++} from '@backstage/plugin-user-settings'; ++import { SidebarSearchModal } from '@backstage/plugin-search'; + import { + Sidebar, +- SidebarPage, + sidebarConfig, +- SidebarContext, +- SidebarItem, + SidebarDivider, +- SidebarSpace, ++ SidebarGroup, ++ SidebarItem, ++ SidebarPage, + SidebarScrollWrapper, ++ SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; ++import MenuIcon from '@material-ui/icons/Menu'; ++import SearchIcon from '@material-ui/icons/Search'; + +@@ -59,3 +46,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -63,8 +50,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -79,19 +61,27 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ } to="/search"> + +- ++ + +- {/* Global nav, not org-specific */} +- +- +- +- +- {/* End global nav */} +- +- +- +- ++ }> ++ {/* Global nav, not org-specific */} ++ ++ ++ ++ ++ {/* End global nav */} ++ ++ ++ ++ ++ + + +- ++ } ++ to="/settings" ++ > ++ ++ + +diff --git a/packages/app/src/components/Root/index.ts b/packages/app/src/components/Root/index.ts +index dff706f..3528881 100644 +--- a/packages/app/src/components/Root/index.ts ++++ b/packages/app/src/components/Root/index.ts +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + export { Root } from './Root'; +diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx +index d3b4b78..6722ea2 100644 +--- a/packages/app/src/components/catalog/EntityPage.tsx ++++ b/packages/app/src/components/catalog/EntityPage.tsx +@@ -1,16 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ + import React from 'react'; +@@ -29,3 +14,2 @@ import { + EntityDependsOnResourcesCard, +- EntitySystemDiagramCard, + EntityHasComponentsCard, +@@ -43,2 +27,4 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +@@ -56,2 +42,27 @@ import { EntityTechdocsContent } from '@backstage/plugin-techdocs'; + import { EmptyState } from '@backstage/core-components'; ++import { ++ Direction, ++ EntityCatalogGraphCard, ++} from '@backstage/plugin-catalog-graph'; ++import { ++ RELATION_API_CONSUMED_BY, ++ RELATION_API_PROVIDED_BY, ++ RELATION_CONSUMES_API, ++ RELATION_DEPENDENCY_OF, ++ RELATION_DEPENDS_ON, ++ RELATION_HAS_PART, ++ RELATION_PART_OF, ++ RELATION_PROVIDES_API, ++} from '@backstage/catalog-model'; ++ ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); + +@@ -94,2 +105,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -110,2 +129,6 @@ const overviewContent = ( + ++ ++ ++ ++ + +@@ -152,3 +175,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -179,3 +202,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -198,3 +221,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -225,2 +248,5 @@ const apiPage = ( + ++ ++ ++ + +@@ -292,3 +318,9 @@ const systemPage = ( + +- ++ ++ ++ ++ ++ ++ ++ + +@@ -304,3 +336,19 @@ const systemPage = ( + +- ++ + +@@ -317,2 +365,5 @@ const domainPage = ( + ++ ++ ++ + +diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx +index cf380b6..1788dde 100644 +--- a/packages/app/src/components/search/SearchPage.tsx ++++ b/packages/app/src/components/search/SearchPage.tsx +@@ -1,7 +1,12 @@ + import React from 'react'; +-import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; ++import { makeStyles, Theme, Grid, Paper } from '@material-ui/core'; + +-import { CatalogResultListItem } from '@backstage/plugin-catalog'; +-import { DocsResultListItem } from '@backstage/plugin-techdocs'; ++import { CatalogSearchResultListItem } from '@backstage/plugin-catalog'; ++import { ++ catalogApiRef, ++ CATALOG_FILTER_EXISTS, ++} from '@backstage/plugin-catalog-react'; ++import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; + ++import { SearchType } from '@backstage/plugin-search'; + import { +@@ -10,5 +15,5 @@ import { + SearchResult, +- SearchType, +- DefaultResultListItem, +-} from '@backstage/plugin-search'; ++ SearchPagination, ++ useSearch, ++} from '@backstage/plugin-search-react'; + import { +@@ -20,2 +25,3 @@ import { + } from '@backstage/core-components'; ++import { useApi } from '@backstage/core-plugin-api'; + +@@ -38,2 +44,4 @@ const SearchPage = () => { + const classes = useStyles(); ++ const { types } = useSearch(); ++ const catalogApi = useApi(catalogApiRef); + +@@ -67,4 +75,26 @@ const SearchPage = () => { + ++ {types.includes('techdocs') && ( ++ { ++ // Return a list of entities which are documented. ++ const { items } = await catalogApi.getEntities({ ++ fields: ['metadata.name'], ++ filter: { ++ 'metadata.annotations.backstage.io/techdocs-ref': ++ CATALOG_FILTER_EXISTS, ++ }, ++ }); ++ ++ const names = items.map(entity => entity.metadata.name); ++ names.sort(); ++ return names; ++ }} ++ /> ++ )} + { + className={classes.filter} ++ label="Lifecycle" + name="lifecycle" +@@ -80,32 +111,6 @@ const SearchPage = () => { + ++ + +- {({ results }) => ( +- +- {results.map(({ type, document }) => { +- switch (type) { +- case 'software-catalog': +- return ( +- +- ); +- case 'techdocs': +- return ( +- +- ); +- default: +- return ( +- +- ); +- } +- })} +- +- )} ++ } /> ++ } /> + +diff --git a/packages/backend/.eslintrc.js b/packages/backend/.eslintrc.js +index 16a033d..e2a53a6 100644 +--- a/packages/backend/.eslintrc.js ++++ b/packages/backend/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint.backend')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index 31231a3..18548e9 100644 +--- a/packages/backend/Dockerfile ++++ b/packages/backend/Dockerfile +@@ -7,3 +7,3 @@ + # yarn tsc +-# yarn build ++# yarn build:backend + # +@@ -11,6 +11,29 @@ + +-FROM node:14-buster-slim ++FROM node:18-bookworm-slim + ++# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends python3 g++ build-essential && \ ++ yarn config set python /usr/bin/python3 ++ ++# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, ++# in which case you should also move better-sqlite3 to "devDependencies" in package.json. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends libsqlite3-dev ++ ++# From here on we use the least-privileged `node` user to run the backend. ++USER node ++ ++# This should create the app dir as `node`. ++# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. ++# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. + WORKDIR /app + ++# This switches many Node.js dependencies to production mode. ++ENV NODE_ENV production ++ + # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. +@@ -18,11 +41,12 @@ WORKDIR /app + # and along with yarn.lock and the root package.json, that's enough to run yarn install. +-COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ ++COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ + RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz + +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + + # Then copy the rest of the backend bundle, along with any other files we might want. +-COPY packages/backend/dist/bundle.tar.gz app-config.yaml ./ ++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"] +diff --git a/packages/backend/README.md b/packages/backend/README.md +index 02426ef..3607b0a 100644 +--- a/packages/backend/README.md ++++ b/packages/backend/README.md +@@ -18,4 +18,2 @@ To run the example backend, first go to the project root and run + yarn install +-yarn tsc +-yarn build + ``` +@@ -27,7 +25,2 @@ After that, go to the `packages/backend` directory and run + ```bash +-AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x \ +-AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x \ +-AUTH_OAUTH2_CLIENT_ID=x AUTH_OAUTH2_CLIENT_SECRET=x \ +-AUTH_OAUTH2_AUTH_URL=x AUTH_OAUTH2_TOKEN_URL=x \ +-LOG_LEVEL=debug \ + yarn start +@@ -35,4 +28,4 @@ yarn start + +-Substitute `x` for actual values, or leave them as dummy values just to try out +-the backend without using the auth or sentry features. ++If you want to override any configuration locally, for example adding any secrets, ++you can do so in `app-config.local.yaml`. + +@@ -45,3 +38,3 @@ locations to the backend. These are places where the backend can find some + entity descriptor data to consume and serve. For more information, see +-[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog). ++[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). + +@@ -65,2 +58,2 @@ and + - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +-- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) ++- [Backstage Documentation](https://backstage.io/docs) +diff --git a/packages/backend/package.json b/packages/backend/package.json +index 8146972..f224eaf 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,28 +6,33 @@ + "private": true, ++ "backstage": { ++ "role": "backend" ++ }, + "scripts": { +- "build": "backstage-cli backend:bundle", +- "build-image": "docker build ../.. -f Dockerfile --tag backstage", +- "start": "backstage-cli backend:dev", +- "lint": "backstage-cli lint", +- "test": "backstage-cli test", +- "clean": "backstage-cli clean", +- "migrate:create": "knex migrate:make -x ts" ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "lint": "backstage-cli package lint", ++ "test": "backstage-cli package test", ++ "clean": "backstage-cli package clean", ++ "build-image": "docker build ../.. -f Dockerfile --tag backstage" + }, + "dependencies": { +- "app": "0.0.0", +- "@backstage/backend-common": "^0.10.2", +- "@backstage/backend-tasks": "^0.1.2", +- "@backstage/catalog-model": "^0.9.8", +- "@backstage/catalog-client": "^0.5.3", +- "@backstage/config": "^0.1.11", +- "@backstage/plugin-app-backend": "^0.3.21", +- "@backstage/plugin-auth-backend": "^0.6.1", +- "@backstage/plugin-catalog-backend": "^0.19.4", +- "@backstage/plugin-proxy-backend": "^0.2.15", +- "@backstage/plugin-scaffolder-backend": "^0.15.19", +- "@backstage/plugin-search-backend": "^0.3.0", +- "@backstage/plugin-search-backend-node": "^0.4.3", +- "@backstage/plugin-techdocs-backend": "^0.12.2", +- "@gitbeaker/node": "^34.6.0", +- "@octokit/rest": "^18.5.3", ++ "@backstage/backend-common": "^0.19.9-next.1", ++ "@backstage/backend-tasks": "^0.5.12-next.1", ++ "@backstage/catalog-client": "^1.4.5", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/config": "^1.1.1", ++ "@backstage/plugin-app-backend": "^0.3.55-next.1", ++ "@backstage/plugin-auth-backend": "^0.20.0-next.1", ++ "@backstage/plugin-auth-node": "^0.4.1-next.1", ++ "@backstage/plugin-catalog-backend": "^1.15.0-next.1", ++ "@backstage/plugin-permission-common": "^0.7.9", ++ "@backstage/plugin-permission-node": "^0.7.18-next.1", ++ "@backstage/plugin-proxy-backend": "^0.4.5-next.1", ++ "@backstage/plugin-scaffolder-backend": "^1.19.0-next.1", ++ "@backstage/plugin-search-backend": "^1.4.7-next.1", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.16-next.1", ++ "@backstage/plugin-search-backend-node": "^1.2.11-next.1", ++ "@backstage/plugin-techdocs-backend": "^1.9.0-next.1", ++ "app": "link:../app", ++ "better-sqlite3": "^8.0.0", + "dockerode": "^3.3.1", +@@ -35,4 +40,3 @@ + "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "pg": "^8.3.0", + "winston": "^3.2.1" +@@ -40,6 +44,7 @@ + "devDependencies": { +- "@backstage/cli": "^0.10.5", ++ "@backstage/cli": "^0.24.0-next.1", + "@types/dockerode": "^3.3.0", + "@types/express": "^4.17.6", +- "@types/express-serve-static-core": "^4.17.5" ++ "@types/express-serve-static-core": "^4.17.5", ++ "@types/luxon": "^2.0.4" + }, +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 08d21e6..04c4ff9 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -17,3 +17,3 @@ import { + DatabaseManager, +- SingleHostDiscovery, ++ HostDiscovery, + UrlReaders, +@@ -31,2 +31,4 @@ import search from './plugins/search'; + import { PluginEnvironment } from './types'; ++import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; + +@@ -35,7 +37,15 @@ function makeCreateEnv(config: Config) { + const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++ const discovery = HostDiscovery.fromConfig(config); + const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++ const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); + const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); ++ const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); ++ ++ const identity = DefaultIdentityClient.create({ ++ discovery, ++ }); ++ const permissions = ServerPermissionClient.fromConfig(config, { ++ discovery, ++ tokenManager, ++ }); + +@@ -57,2 +67,4 @@ function makeCreateEnv(config: Config) { + scheduler, ++ permissions, ++ identity, + }; +@@ -100,3 +112,3 @@ module.hot?.accept(); + main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); ++ console.error('Backend failed to start up', error); + process.exit(1); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +index 07fb04f..7c37f68 100644 +--- a/packages/backend/src/plugins/app.ts ++++ b/packages/backend/src/plugins/app.ts +@@ -4,9 +4,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + return await createRouter({ +- logger, +- config, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, + appPackageName: 'app', +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +index 5216510..77eb6aa 100644 +--- a/packages/backend/src/plugins/auth.ts ++++ b/packages/backend/src/plugins/auth.ts +@@ -1,2 +1,6 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; ++import { ++ createRouter, ++ providers, ++ defaultAuthProviderFactories, ++} from '@backstage/plugin-auth-backend'; + import { Router } from 'express'; +@@ -4,9 +8,47 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, database, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, ++ providerFactories: { ++ ...defaultAuthProviderFactories, ++ ++ // This replaces the default GitHub auth provider with a customized one. ++ // The `signIn` option enables sign-in for this provider, using the ++ // identity resolution logic that's provided in the `resolver` callback. ++ // ++ // This particular resolver makes all users share a single "guest" identity. ++ // It should only be used for testing and trying out Backstage. ++ // ++ // If you want to use a production ready resolver you can switch to ++ // the one that is commented out below, it looks up a user entity in the ++ // catalog using the GitHub username of the authenticated user. ++ // That resolver requires you to have user entities populated in the catalog, ++ // for example using https://backstage.io/docs/integrations/github/org ++ // ++ // There are other resolvers to choose from, and you can also create ++ // your own, see the auth documentation for more details: ++ // ++ // https://backstage.io/docs/auth/identity-resolver ++ github: providers.github.create({ ++ signIn: { ++ resolver(_, ctx) { ++ const userRef = 'user:default/guest'; // Must be a full entity reference ++ return ctx.issueToken({ ++ claims: { ++ sub: userRef, // The user's own identity ++ ent: [userRef], // A list of identities that the user claims ownership through ++ }, ++ }); ++ }, ++ // resolver: providers.github.resolvers.usernameMatchingUserEntityName(), ++ }, ++ }), ++ }, ++ }); + } +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +index 506f6d9..54ec393 100644 +--- a/packages/backend/src/plugins/proxy.ts ++++ b/packages/backend/src/plugins/proxy.ts +@@ -4,8 +4,10 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ }); + } +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +index 6be2e97..a12fee2 100644 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ b/packages/backend/src/plugins/scaffolder.ts +@@ -1,5 +1,3 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; + import { CatalogClient } from '@backstage/catalog-client'; + import { createRouter } from '@backstage/plugin-scaffolder-backend'; +-import Docker from 'dockerode'; + import { Router } from 'express'; +@@ -7,20 +5,17 @@ import type { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- database, +- reader, +- discovery, +-}: PluginEnvironment): Promise { +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- const catalogClient = new CatalogClient({ discoveryApi: discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ const catalogClient = new CatalogClient({ ++ discoveryApi: env.discovery, ++ }); + + return await createRouter({ +- containerRunner, +- logger, +- config, +- database, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ reader: env.reader, + catalogClient, +- reader, ++ identity: env.identity, ++ permissions: env.permissions, + }); +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +index f23b0c7..e9469dc 100644 +--- a/packages/backend/src/plugins/search.ts ++++ b/packages/backend/src/plugins/search.ts +@@ -7,14 +7,25 @@ import { + import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend'; ++import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend'; ++import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; ++import { Router } from 'express'; + +-export default async function createPlugin({ +- logger, +- discovery, +- config, +- tokenManager, +-}: PluginEnvironment) { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); ++ const searchEngine = new LunrSearchEngine({ ++ logger: env.logger, ++ }); ++ const indexBuilder = new IndexBuilder({ ++ logger: env.logger, ++ searchEngine, ++ }); ++ ++ const schedule = env.scheduler.createScheduledTaskRunner({ ++ frequency: { minutes: 10 }, ++ timeout: { minutes: 15 }, ++ // A 3 second delay gives the backend server a chance to initialize before ++ // any collators are executed, which may attempt requests against the API. ++ initialDelay: { seconds: 3 }, ++ }); + +@@ -23,6 +34,6 @@ export default async function createPlugin({ + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { +- discovery, +- tokenManager, ++ schedule, ++ factory: DefaultCatalogCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, + }), +@@ -32,7 +43,7 @@ export default async function createPlugin({ + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultTechDocsCollator.fromConfig(config, { +- discovery, +- logger, +- tokenManager, ++ schedule, ++ factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ logger: env.logger, ++ tokenManager: env.tokenManager, + }), +@@ -43,6 +54,4 @@ export default async function createPlugin({ + const { scheduler } = await indexBuilder.build(); ++ scheduler.start(); + +- // A 3 second delay gives the backend server a chance to initialize before +- // any collators are executed, which may attempt requests against the API. +- setTimeout(() => scheduler.start(), 3000); + useHotCleanup(module, () => scheduler.stop()); +@@ -51,3 +60,6 @@ export default async function createPlugin({ + engine: indexBuilder.getSearchEngine(), +- logger, ++ types: indexBuilder.getDocumentTypes(), ++ permissions: env.permissions, ++ config: env.config, ++ logger: env.logger, + }); +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +index 054c64d..be8bb0c 100644 +--- a/packages/backend/src/plugins/techdocs.ts ++++ b/packages/backend/src/plugins/techdocs.ts +@@ -11,13 +11,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +- cache, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, ++ const preparers = await Preparers.fromConfig(env.config, { ++ logger: env.logger, ++ reader: env.reader, + }); +@@ -29,4 +25,4 @@ export default async function createPlugin({ + // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, ++ const generators = await Generators.fromConfig(env.config, { ++ logger: env.logger, + containerRunner, +@@ -37,5 +33,5 @@ export default async function createPlugin({ + // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, ++ const publisher = await Publisher.fromConfig(env.config, { ++ logger: env.logger, ++ discovery: env.discovery, + }); +@@ -49,6 +45,6 @@ export default async function createPlugin({ + publisher, +- logger, +- config, +- discovery, +- cache, ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ cache: env.cache, + }); +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +index c3d0158..9cd2c74 100644 +--- a/packages/backend/src/types.ts ++++ b/packages/backend/src/types.ts +@@ -10,2 +10,4 @@ import { + import { PluginTaskScheduler } from '@backstage/backend-tasks'; ++import { PermissionEvaluator } from '@backstage/plugin-permission-common'; ++import { IdentityApi } from '@backstage/plugin-auth-node'; + +@@ -20,2 +22,4 @@ export type PluginEnvironment = { + scheduler: PluginTaskScheduler; ++ permissions: PermissionEvaluator; ++ identity: IdentityApi; + }; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..37c7fb1 +--- /dev/null ++++ b/playwright.config.ts +@@ -0,0 +1,60 @@ ++/* ++ * Copyright 2023 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { defineConfig } from '@playwright/test'; ++import { generateProjects } from '@backstage/e2e-test-utils/playwright'; ++ ++/** ++ * See https://playwright.dev/docs/test-configuration. ++ */ ++export default defineConfig({ ++ timeout: 60_000, ++ ++ expect: { ++ timeout: 5_000, ++ }, ++ ++ // Run your local dev server before starting the tests ++ webServer: process.env.CI ++ ? [] ++ : [ ++ { ++ command: 'yarn start', ++ port: 3000, ++ reuseExistingServer: true, ++ timeout: 60_000, ++ }, ++ ], ++ ++ forbidOnly: !!process.env.CI, ++ ++ retries: process.env.CI ? 2 : 0, ++ ++ reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]], ++ ++ use: { ++ actionTimeout: 0, ++ baseURL: ++ process.env.PLAYWRIGHT_URL ?? ++ (process.env.CI ? 'http://localhost:7007' : 'http://localhost:3000'), ++ screenshot: 'only-on-failure', ++ trace: 'on-first-retry', ++ }, ++ ++ outputDir: 'node_modules/.cache/e2e-test-results', ++ ++ projects: generateProjects(), // Find all packages with e2e-test folders ++}); +diff --git a/plugins/README.md b/plugins/README.md +new file mode 100644 +index 0000000..d7865fd +--- /dev/null ++++ b/plugins/README.md +@@ -0,0 +1,9 @@ ++# The Plugins Folder ++ ++This is where your own plugins and their associated modules live, each in a ++separate folder of its own. ++ ++If you want to create a new plugin here, go to your project root directory, run ++the command `yarn new`, and follow the on-screen instructions. ++ ++You can also check out existing plugins on [the plugin marketplace](https://backstage.io/plugins)! +diff --git a/yarn.lock b/yarn.lock +new file mode 100644 +index 0000000..5ad7fe2 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1 @@ ++# intentionally left empty diff --git a/diffs/0.4.12..0.5.7-next.1.diff b/diffs/0.4.12..0.5.7-next.1.diff new file mode 100644 index 00000000..c755429e --- /dev/null +++ b/diffs/0.4.12..0.5.7-next.1.diff @@ -0,0 +1,1831 @@ +diff --git a/.dockerignore b/.dockerignore +index 63c9c34..05edb62 100644 +--- a/.dockerignore ++++ b/.dockerignore +@@ -1,5 +1,8 @@ + .git ++.yarn/cache ++.yarn/install-state.gz + node_modules +-packages +-!packages/backend/dist ++packages/*/src ++packages/*/node_modules + plugins ++*.local.yaml +diff --git a/.eslintignore b/.eslintignore +new file mode 100644 +index 0000000..e5b1994 +--- /dev/null ++++ b/.eslintignore +@@ -0,0 +1 @@ ++playwright.config.ts +diff --git a/.gitignore b/.gitignore +index d16a8d3..fbf8139 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -17,2 +17,11 @@ node_modules/ + ++# Yarn 3 files ++.pnp.* ++.yarn/* ++!.yarn/patches ++!.yarn/plugins ++!.yarn/releases ++!.yarn/sdks ++!.yarn/versions ++ + # Node version directives +@@ -39 +48,7 @@ site + *-credentials.yaml ++ ++# vscode database functionality support files ++*.session.sql ++ ++# E2E test reports ++e2e-test-report/ +diff --git a/app-config.production.yaml b/app-config.production.yaml +index 5e36c23..8f0751c 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -1,3 +1,3 @@ + app: +- # Should be the same as backend.baseUrl when using the `app-backend` plugin ++ # Should be the same as backend.baseUrl when using the `app-backend` plugin. + baseUrl: http://localhost:7007 +@@ -5,4 +5,31 @@ app: + backend: ++ # Note that the baseUrl should be the URL that the browser and other clients ++ # should use when communicating with the backend, i.e. it needs to be ++ # reachable not just from within the backend host, but from all of your ++ # callers. When its value is "http://localhost:7007", it's strictly private ++ # and can't be reached by others. + baseUrl: http://localhost:7007 +- listen: +- port: 7007 ++ # The listener can also be expressed as a single : string. In this case we bind to ++ # all interfaces, the most permissive setting. The right value depends on your specific deployment. ++ listen: ':7007' ++ ++ # config options: https://node-postgres.com/api/client ++ database: ++ client: pg ++ connection: ++ host: ${POSTGRES_HOST} ++ port: ${POSTGRES_PORT} ++ user: ${POSTGRES_USER} ++ password: ${POSTGRES_PASSWORD} ++ # https://node-postgres.com/features/ssl ++ # you can set the sslmode configuration option via the `PGSSLMODE` environment variable ++ # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require) ++ # ssl: ++ # ca: # if you have a CA file and want to verify it you can uncomment this section ++ # $file: /ca/server.crt ++ ++catalog: ++ # Overrides the default list locations from app-config.yaml as these contain example data. ++ # See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details ++ # on how to get entities into the catalog. ++ locations: [] +diff --git a/app-config.yaml b/app-config.yaml +index 1a622a2..08d2abf 100644 +--- a/app-config.yaml ++++ b/app-config.yaml +@@ -9,3 +9,4 @@ backend: + # Used for enabling authentication, secret is shared by all backend plugins +- # See backend-to-backend-auth.md in the docs for information on the format ++ # See https://backstage.io/docs/auth/service-to-service-auth for ++ # information on the format + # auth: +@@ -16,2 +17,4 @@ backend: + port: 7007 ++ # Uncomment the following host directive to bind to specific interfaces ++ # host: 127.0.0.1 + csp: +@@ -22,9 +25,9 @@ backend: + origin: http://localhost:3000 +- methods: [GET, POST, PUT, DELETE] ++ methods: [GET, HEAD, PATCH, POST, PUT, DELETE] + credentials: true ++ # This is for local development only, it is not recommended to use this in production ++ # The production database configuration is stored in app-config.production.yaml + database: +- client: sqlite3 ++ client: better-sqlite3 + connection: ':memory:' +- cache: +- store: memory + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir +@@ -34,2 +37,4 @@ integrations: + - host: github.com ++ # This is a Personal Access Token or PAT from GitHub. You can find out how to generate this token, and more information ++ # about setting up the GitHub integration here: https://backstage.io/docs/getting-started/configuration#setting-up-a-github-integration + token: ${GITHUB_TOKEN} +@@ -41,5 +46,8 @@ integrations: + proxy: +- '/test': +- target: 'https://example.com' +- changeOrigin: true ++ ### Example for how to add a proxy endpoint for the frontend. ++ ### A typical reason to do this is to handle HTTPS and CORS for internal services. ++ # endpoints: ++ # '/test': ++ # target: 'https://example.com' ++ # changeOrigin: true + +@@ -64,41 +72,32 @@ scaffolder: + catalog: ++ import: ++ entityFilename: catalog-info.yaml ++ pullRequestBranchName: backstage-integration + rules: +- - allow: [Component, System, API, Group, User, Resource, Location] ++ - allow: [Component, System, API, Resource, Location] + locations: +- # Backstage example components +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-components.yaml ++ # Local example data, file locations are relative to the backend process, typically `packages/backend` ++ - type: file ++ target: ../../examples/entities.yaml + +- # Backstage example systems +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-systems.yaml +- +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml +- +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml +- +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.yaml +- +- # Backstage example templates +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/react-ssr-template/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/springboot-grpc-template/template.yaml ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml + rules: + - allow: [Template] +- - type: url +- target: https://github.com/spotify/cookiecutter-golang/blob/master/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/docs-template/template.yaml ++ ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml + rules: +- - allow: [Template] ++ - allow: [User, Group] ++ ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml ++ ++ ## Uncomment these lines to add an example org ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme-corp.yaml ++ # rules: ++ # - allow: [User, Group] +diff --git a/backstage.json b/backstage.json +index d3585dd..707af29 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "0.4.12" ++ "version": "1.20.0-next.1" + } +diff --git a/examples/entities.yaml b/examples/entities.yaml +new file mode 100644 +index 0000000..447e8b1 +--- /dev/null ++++ b/examples/entities.yaml +@@ -0,0 +1,41 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system ++apiVersion: backstage.io/v1alpha1 ++kind: System ++metadata: ++ name: examples ++spec: ++ owner: guests ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: example-website ++spec: ++ type: website ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ providesApis: [example-grpc-api] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api ++apiVersion: backstage.io/v1alpha1 ++kind: API ++metadata: ++ name: example-grpc-api ++spec: ++ type: grpc ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ definition: | ++ syntax = "proto3"; ++ ++ service Exampler { ++ rpc Example (ExampleMessage) returns (ExampleMessage) {}; ++ } ++ ++ message ExampleMessage { ++ string example = 1; ++ }; +diff --git a/examples/org.yaml b/examples/org.yaml +new file mode 100644 +index 0000000..a10e81f +--- /dev/null ++++ b/examples/org.yaml +@@ -0,0 +1,17 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user ++apiVersion: backstage.io/v1alpha1 ++kind: User ++metadata: ++ name: guest ++spec: ++ memberOf: [guests] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group ++apiVersion: backstage.io/v1alpha1 ++kind: Group ++metadata: ++ name: guests ++spec: ++ type: team ++ children: [] +diff --git a/examples/template/content/catalog-info.yaml b/examples/template/content/catalog-info.yaml +new file mode 100644 +index 0000000..d4ccca4 +--- /dev/null ++++ b/examples/template/content/catalog-info.yaml +@@ -0,0 +1,8 @@ ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: ${{ values.name | dump }} ++spec: ++ type: service ++ owner: user:guest ++ lifecycle: experimental +diff --git a/examples/template/content/index.js b/examples/template/content/index.js +new file mode 100644 +index 0000000..071ce5a +--- /dev/null ++++ b/examples/template/content/index.js +@@ -0,0 +1 @@ ++console.log('Hello from ${{ values.name }}!'); +diff --git a/examples/template/content/package.json b/examples/template/content/package.json +new file mode 100644 +index 0000000..86f968a +--- /dev/null ++++ b/examples/template/content/package.json +@@ -0,0 +1,5 @@ ++{ ++ "name": "${{ values.name }}", ++ "private": true, ++ "dependencies": {} ++} +diff --git a/examples/template/template.yaml b/examples/template/template.yaml +new file mode 100644 +index 0000000..33f262b +--- /dev/null ++++ b/examples/template/template.yaml +@@ -0,0 +1,74 @@ ++apiVersion: scaffolder.backstage.io/v1beta3 ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template ++kind: Template ++metadata: ++ name: example-nodejs-template ++ title: Example Node.js Template ++ description: An example template for the scaffolder that creates a simple Node.js service ++spec: ++ owner: user:guest ++ type: service ++ ++ # These parameters are used to generate the input form in the frontend, and are ++ # used to gather input data for the execution of the template. ++ parameters: ++ - title: Fill in some steps ++ required: ++ - name ++ properties: ++ name: ++ title: Name ++ type: string ++ description: Unique name of the component ++ ui:autofocus: true ++ ui:options: ++ rows: 5 ++ - title: Choose a location ++ required: ++ - repoUrl ++ properties: ++ repoUrl: ++ title: Repository Location ++ type: string ++ ui:field: RepoUrlPicker ++ ui:options: ++ allowedHosts: ++ - github.com ++ ++ # These steps are executed in the scaffolder backend, using data that we gathered ++ # via the parameters above. ++ steps: ++ # Each step executes an action, in this case one templates files into the working directory. ++ - id: fetch-base ++ name: Fetch Base ++ action: fetch:template ++ input: ++ url: ./content ++ values: ++ name: ${{ parameters.name }} ++ ++ # This step publishes the contents of the working directory to GitHub. ++ - id: publish ++ name: Publish ++ action: publish:github ++ input: ++ allowedHosts: ['github.com'] ++ description: This is ${{ parameters.name }} ++ repoUrl: ${{ parameters.repoUrl }} ++ ++ # The final step is to register our new component in the catalog. ++ - id: register ++ name: Register ++ action: catalog:register ++ input: ++ repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} ++ catalogInfoPath: '/catalog-info.yaml' ++ ++ # Outputs are displayed to the user after a successful execution of the template. ++ output: ++ links: ++ - title: Repository ++ url: ${{ steps['publish'].output.remoteUrl }} ++ - title: Open in catalog ++ icon: catalog ++ entityRef: ${{ steps['register'].output.entityRef }} +diff --git a/lerna.json b/lerna.json +index 322929d..529a62f 100644 +--- a/lerna.json ++++ b/lerna.json +@@ -3,4 +3,4 @@ + "npmClient": "yarn", +- "useWorkspaces": true, +- "version": "0.1.0" ++ "version": "0.1.0", ++ "$schema": "node_modules/lerna/schemas/lerna-schema.json" + } +diff --git a/package.json b/package.json +index e594f58..2364da8 100644 +--- a/package.json ++++ b/package.json +@@ -5,3 +5,3 @@ + "engines": { +- "node": "14 || 16" ++ "node": "18 || 20" + }, +@@ -11,3 +11,4 @@ + "start-backend": "yarn workspace backend start", +- "build": "lerna run build", ++ "build:backend": "yarn workspace backend build", ++ "build:all": "backstage-cli repo build --all", + "build-image": "yarn workspace backend build-image", +@@ -15,11 +16,11 @@ + "tsc:full": "tsc --skipLibCheck false --incremental false", +- "clean": "backstage-cli clean && lerna run clean", +- "diff": "lerna run diff --", +- "test": "backstage-cli test", +- "test:all": "lerna run test -- --coverage", +- "lint": "lerna run lint --since origin/master --", +- "lint:all": "lerna run lint --", ++ "clean": "backstage-cli repo clean", ++ "test": "backstage-cli repo test", ++ "test:all": "backstage-cli repo test --coverage", ++ "test:e2e": "playwright test", ++ "fix": "backstage-cli repo fix", ++ "lint": "backstage-cli repo lint --since origin/master", ++ "lint:all": "backstage-cli repo lint", + "prettier:check": "prettier --check .", +- "create-plugin": "backstage-cli create-plugin --scope internal", +- "remove-plugin": "backstage-cli remove-plugin" ++ "new": "backstage-cli new --scope internal" + }, +@@ -32,7 +33,15 @@ + "devDependencies": { +- "@backstage/cli": "^0.11.0", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/e2e-test-utils": "^0.1.0", ++ "@playwright/test": "^1.32.3", + "@spotify/prettier-config": "^12.0.0", +- "concurrently": "^6.0.0", +- "lerna": "^4.0.0", +- "prettier": "^2.3.2" ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^9.0.0", ++ "prettier": "^2.3.2", ++ "typescript": "~5.2.0" ++ }, ++ "resolutions": { ++ "@types/react": "^17", ++ "@types/react-dom": "^17" + }, +diff --git a/packages/README.md b/packages/README.md +new file mode 100644 +index 0000000..6327fa0 +--- /dev/null ++++ b/packages/README.md +@@ -0,0 +1,9 @@ ++# The Packages Folder ++ ++This is where your own applications and centrally managed libraries live, each ++in a separate folder of its own. ++ ++From the start there's an `app` folder (for the frontend) and a `backend` folder ++(for the Node backend), but you can also add more modules in here that house ++your core additions and adaptations, such as themes, common React component ++libraries, utilities, and similar. +diff --git a/packages/app/.eslintignore b/packages/app/.eslintignore +new file mode 100644 +index 0000000..a48cf0d +--- /dev/null ++++ b/packages/app/.eslintignore +@@ -0,0 +1 @@ ++public +diff --git a/packages/app/.eslintrc.js b/packages/app/.eslintrc.js +index 13573ef..e2a53a6 100644 +--- a/packages/app/.eslintrc.js ++++ b/packages/app/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/app/cypress.json b/packages/app/cypress.json +deleted file mode 100644 +index 5de7ebf..0000000 +--- a/packages/app/cypress.json ++++ /dev/null +@@ -1,5 +0,0 @@ +-{ +- "baseUrl": "http://localhost:3001", +- "fixturesFolder": false, +- "pluginsFile": false +-} +diff --git a/packages/app/cypress/.eslintrc.json b/packages/app/cypress/.eslintrc.json +deleted file mode 100644 +index 2b3a458..0000000 +--- a/packages/app/cypress/.eslintrc.json ++++ /dev/null +@@ -1,21 +0,0 @@ +-{ +- "plugins": ["cypress"], +- "extends": ["plugin:cypress/recommended"], +- "rules": { +- "jest/expect-expect": [ +- "error", +- { +- "assertFunctionNames": ["expect", "cy.contains"] +- } +- ], +- "import/no-extraneous-dependencies": [ +- "error", +- { +- "devDependencies": true, +- "optionalDependencies": true, +- "peerDependencies": true, +- "bundledDependencies": true +- } +- ] +- } +-} +diff --git a/packages/app/cypress/integration/app.js b/packages/app/cypress/integration/app.js +deleted file mode 100644 +index 43fb2e3..0000000 +--- a/packages/app/cypress/integration/app.js ++++ /dev/null +@@ -1,6 +0,0 @@ +-describe('App', () => { +- it('should render the catalog', () => { +- cy.visit('/'); +- cy.contains('My Company Catalog'); +- }); +-}); +diff --git a/packages/app/e2e-tests/app.test.ts b/packages/app/e2e-tests/app.test.ts +new file mode 100644 +index 0000000..d45bc0d +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,23 @@ ++/* ++ * Copyright 2020 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { test, expect } from '@playwright/test'; ++ ++test('App should render the welcome page', async ({ page }) => { ++ await page.goto('/'); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 6fbdef4..2100405 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,22 +5,38 @@ + "bundled": true, ++ "backstage": { ++ "role": "frontend" ++ }, ++ "scripts": { ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "clean": "backstage-cli package clean", ++ "test": "backstage-cli package test", ++ "lint": "backstage-cli package lint" ++ }, + "dependencies": { +- "@backstage/app-defaults": "^0.1.4", +- "@backstage/catalog-model": "^0.9.9", +- "@backstage/cli": "^0.11.0", +- "@backstage/core-app-api": "^0.4.0", +- "@backstage/core-components": "^0.8.4", +- "@backstage/core-plugin-api": "^0.5.0", +- "@backstage/integration-react": "^0.1.18", +- "@backstage/plugin-api-docs": "^0.6.22", +- "@backstage/plugin-catalog": "^0.7.8", +- "@backstage/plugin-catalog-import": "^0.7.9", +- "@backstage/plugin-catalog-react": "^0.6.11", +- "@backstage/plugin-github-actions": "^0.4.31", +- "@backstage/plugin-org": "^0.3.34", +- "@backstage/plugin-scaffolder": "^0.11.18", +- "@backstage/plugin-search": "^0.5.5", +- "@backstage/plugin-tech-radar": "^0.5.2", +- "@backstage/plugin-techdocs": "^0.12.14", +- "@backstage/plugin-user-settings": "^0.3.16", +- "@backstage/theme": "^0.2.14", ++ "@backstage/app-defaults": "^1.4.5-next.1", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/core-app-api": "^1.11.1-next.0", ++ "@backstage/core-components": "^0.13.8-next.1", ++ "@backstage/core-plugin-api": "^1.8.0-next.0", ++ "@backstage/integration-react": "^1.1.21-next.1", ++ "@backstage/plugin-api-docs": "^0.9.13-next.1", ++ "@backstage/plugin-catalog": "^1.15.0-next.1", ++ "@backstage/plugin-catalog-common": "^1.0.17", ++ "@backstage/plugin-catalog-graph": "^0.2.38-next.1", ++ "@backstage/plugin-catalog-import": "^0.10.2-next.1", ++ "@backstage/plugin-catalog-react": "^1.9.0-next.1", ++ "@backstage/plugin-github-actions": "^0.6.7-next.1", ++ "@backstage/plugin-org": "^0.6.16-next.1", ++ "@backstage/plugin-permission-react": "^0.4.17-next.0", ++ "@backstage/plugin-scaffolder": "^1.16.0-next.1", ++ "@backstage/plugin-search": "^1.4.2-next.1", ++ "@backstage/plugin-search-react": "^1.7.2-next.1", ++ "@backstage/plugin-tech-radar": "^0.6.10-next.1", ++ "@backstage/plugin-techdocs": "^1.8.1-next.1", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.2-next.1", ++ "@backstage/plugin-techdocs-react": "^1.1.13-next.1", ++ "@backstage/plugin-user-settings": "^0.7.12-next.1", ++ "@backstage/theme": "^0.4.4-next.0", + "@material-ui/core": "^4.12.2", +@@ -28,31 +44,17 @@ + "history": "^5.0.0", +- "react": "^16.13.1", +- "react-dom": "^16.13.1", +- "react-router": "6.0.0-beta.0", +- "react-router-dom": "6.0.0-beta.0", +- "react-use": "^15.3.3" ++ "react": "^17.0.2", ++ "react-dom": "^17.0.2", ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^0.2.2", ++ "@backstage/test-utils": "^1.4.5-next.0", ++ "@playwright/test": "^1.32.3", + "@testing-library/jest-dom": "^5.10.1", +- "@testing-library/react": "^10.4.1", +- "@testing-library/user-event": "^12.0.7", +- "@types/jest": "^26.0.7", +- "@types/node": "^14.14.32", ++ "@testing-library/react": "^12.1.3", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^8.0.0", + "@types/react-dom": "*", +- "cross-env": "^7.0.0", +- "cypress": "^7.3.0", +- "eslint-plugin-cypress": "^2.10.3", +- "start-server-and-test": "^1.10.11" +- }, +- "scripts": { +- "start": "backstage-cli app:serve", +- "build": "backstage-cli app:build", +- "clean": "backstage-cli clean", +- "test": "backstage-cli test", +- "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", +- "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", +- "lint": "backstage-cli lint", +- "cy:dev": "cypress open", +- "cy:run": "cypress run" ++ "cross-env": "^7.0.0" + }, +diff --git a/packages/app/public/index.html b/packages/app/public/index.html +index a936c73..fc758ee 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -10,3 +10,2 @@ + /> +- + +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 8a53583..8d62f29 100644 +--- a/packages/app/src/App.tsx ++++ b/packages/app/src/App.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { Navigate, Route } from 'react-router'; ++import { Navigate, Route } from 'react-router-dom'; + import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; +@@ -13,2 +13,3 @@ import { + import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; ++import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +@@ -16,3 +17,2 @@ import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -21,2 +21,4 @@ import { + } from '@backstage/plugin-techdocs'; ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; + import { UserSettingsPage } from '@backstage/plugin-user-settings'; +@@ -29,3 +31,6 @@ import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; + import { createApp } from '@backstage/app-defaults'; +-import { FlatRoutes } from '@backstage/core-app-api'; ++import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; ++import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; ++import { RequirePermission } from '@backstage/plugin-permission-react'; ++import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; + +@@ -37,5 +42,6 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); + bind(apiDocsPlugin.externalRoutes, { +- createComponent: scaffolderPlugin.routes.root, ++ registerApi: catalogImportPlugin.routes.importPage, + }); +@@ -43,2 +49,6 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, ++ }); ++ bind(orgPlugin.externalRoutes, { ++ catalogIndex: catalogPlugin.routes.catalogIndex, + }); +@@ -47,8 +57,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -60,5 +67,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -73,3 +82,10 @@ const routes = ( + /> +- } /> ++ ++ ++ ++ } ++ /> + }> +@@ -78,2 +94,3 @@ const routes = ( + } /> ++ } /> + +@@ -81,4 +98,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -88,5 +105,3 @@ const App = () => ( + +- ++ , + ); +- +-export default App; +diff --git a/packages/app/src/components/Root/LogoFull.tsx b/packages/app/src/components/Root/LogoFull.tsx +index c7b1c84..47e3b73 100644 +--- a/packages/app/src/components/Root/LogoFull.tsx ++++ b/packages/app/src/components/Root/LogoFull.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/LogoIcon.tsx b/packages/app/src/components/Root/LogoIcon.tsx +index 073cf6e..7eae8c7 100644 +--- a/packages/app/src/components/Root/LogoIcon.tsx ++++ b/packages/app/src/components/Root/LogoIcon.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx +index b4fa04f..6768b48 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,19 +1,3 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- +-import React, { useContext, PropsWithChildren } from 'react'; +-import { Link, makeStyles } from '@material-ui/core'; ++import React, { PropsWithChildren } from 'react'; ++import { makeStyles } from '@material-ui/core'; + import HomeIcon from '@material-ui/icons/Home'; +@@ -25,18 +9,21 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; +-import { Settings as SidebarSettings } from '@backstage/plugin-user-settings'; + import { +- SidebarSearchModal, +- SearchContextProvider, +-} from '@backstage/plugin-search'; ++ Settings as SidebarSettings, ++ UserSettingsSignInAvatar, ++} from '@backstage/plugin-user-settings'; ++import { SidebarSearchModal } from '@backstage/plugin-search'; + import { + Sidebar, +- SidebarPage, + sidebarConfig, +- SidebarContext, +- SidebarItem, + SidebarDivider, +- SidebarSpace, ++ SidebarGroup, ++ SidebarItem, ++ SidebarPage, + SidebarScrollWrapper, ++ SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; ++import MenuIcon from '@material-ui/icons/Menu'; ++import SearchIcon from '@material-ui/icons/Search'; + +@@ -59,3 +46,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -63,8 +50,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -79,19 +61,27 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ } to="/search"> + +- ++ + +- {/* Global nav, not org-specific */} +- +- +- +- +- {/* End global nav */} +- +- +- +- ++ }> ++ {/* Global nav, not org-specific */} ++ ++ ++ ++ ++ {/* End global nav */} ++ ++ ++ ++ ++ + + +- ++ } ++ to="/settings" ++ > ++ ++ + +diff --git a/packages/app/src/components/Root/index.ts b/packages/app/src/components/Root/index.ts +index dff706f..3528881 100644 +--- a/packages/app/src/components/Root/index.ts ++++ b/packages/app/src/components/Root/index.ts +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + export { Root } from './Root'; +diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx +index d3b4b78..6722ea2 100644 +--- a/packages/app/src/components/catalog/EntityPage.tsx ++++ b/packages/app/src/components/catalog/EntityPage.tsx +@@ -1,16 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ + import React from 'react'; +@@ -29,3 +14,2 @@ import { + EntityDependsOnResourcesCard, +- EntitySystemDiagramCard, + EntityHasComponentsCard, +@@ -43,2 +27,4 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +@@ -56,2 +42,27 @@ import { EntityTechdocsContent } from '@backstage/plugin-techdocs'; + import { EmptyState } from '@backstage/core-components'; ++import { ++ Direction, ++ EntityCatalogGraphCard, ++} from '@backstage/plugin-catalog-graph'; ++import { ++ RELATION_API_CONSUMED_BY, ++ RELATION_API_PROVIDED_BY, ++ RELATION_CONSUMES_API, ++ RELATION_DEPENDENCY_OF, ++ RELATION_DEPENDS_ON, ++ RELATION_HAS_PART, ++ RELATION_PART_OF, ++ RELATION_PROVIDES_API, ++} from '@backstage/catalog-model'; ++ ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); + +@@ -94,2 +105,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -110,2 +129,6 @@ const overviewContent = ( + ++ ++ ++ ++ + +@@ -152,3 +175,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -179,3 +202,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -198,3 +221,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -225,2 +248,5 @@ const apiPage = ( + ++ ++ ++ + +@@ -292,3 +318,9 @@ const systemPage = ( + +- ++ ++ ++ ++ ++ ++ ++ + +@@ -304,3 +336,19 @@ const systemPage = ( + +- ++ + +@@ -317,2 +365,5 @@ const domainPage = ( + ++ ++ ++ + +diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx +index cf380b6..1788dde 100644 +--- a/packages/app/src/components/search/SearchPage.tsx ++++ b/packages/app/src/components/search/SearchPage.tsx +@@ -1,7 +1,12 @@ + import React from 'react'; +-import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; ++import { makeStyles, Theme, Grid, Paper } from '@material-ui/core'; + +-import { CatalogResultListItem } from '@backstage/plugin-catalog'; +-import { DocsResultListItem } from '@backstage/plugin-techdocs'; ++import { CatalogSearchResultListItem } from '@backstage/plugin-catalog'; ++import { ++ catalogApiRef, ++ CATALOG_FILTER_EXISTS, ++} from '@backstage/plugin-catalog-react'; ++import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; + ++import { SearchType } from '@backstage/plugin-search'; + import { +@@ -10,5 +15,5 @@ import { + SearchResult, +- SearchType, +- DefaultResultListItem, +-} from '@backstage/plugin-search'; ++ SearchPagination, ++ useSearch, ++} from '@backstage/plugin-search-react'; + import { +@@ -20,2 +25,3 @@ import { + } from '@backstage/core-components'; ++import { useApi } from '@backstage/core-plugin-api'; + +@@ -38,2 +44,4 @@ const SearchPage = () => { + const classes = useStyles(); ++ const { types } = useSearch(); ++ const catalogApi = useApi(catalogApiRef); + +@@ -67,4 +75,26 @@ const SearchPage = () => { + ++ {types.includes('techdocs') && ( ++ { ++ // Return a list of entities which are documented. ++ const { items } = await catalogApi.getEntities({ ++ fields: ['metadata.name'], ++ filter: { ++ 'metadata.annotations.backstage.io/techdocs-ref': ++ CATALOG_FILTER_EXISTS, ++ }, ++ }); ++ ++ const names = items.map(entity => entity.metadata.name); ++ names.sort(); ++ return names; ++ }} ++ /> ++ )} + { + className={classes.filter} ++ label="Lifecycle" + name="lifecycle" +@@ -80,32 +111,6 @@ const SearchPage = () => { + ++ + +- {({ results }) => ( +- +- {results.map(({ type, document }) => { +- switch (type) { +- case 'software-catalog': +- return ( +- +- ); +- case 'techdocs': +- return ( +- +- ); +- default: +- return ( +- +- ); +- } +- })} +- +- )} ++ } /> ++ } /> + +diff --git a/packages/backend/.eslintrc.js b/packages/backend/.eslintrc.js +index 16a033d..e2a53a6 100644 +--- a/packages/backend/.eslintrc.js ++++ b/packages/backend/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint.backend')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index 31231a3..18548e9 100644 +--- a/packages/backend/Dockerfile ++++ b/packages/backend/Dockerfile +@@ -7,3 +7,3 @@ + # yarn tsc +-# yarn build ++# yarn build:backend + # +@@ -11,6 +11,29 @@ + +-FROM node:14-buster-slim ++FROM node:18-bookworm-slim + ++# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends python3 g++ build-essential && \ ++ yarn config set python /usr/bin/python3 ++ ++# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, ++# in which case you should also move better-sqlite3 to "devDependencies" in package.json. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends libsqlite3-dev ++ ++# From here on we use the least-privileged `node` user to run the backend. ++USER node ++ ++# This should create the app dir as `node`. ++# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. ++# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. + WORKDIR /app + ++# This switches many Node.js dependencies to production mode. ++ENV NODE_ENV production ++ + # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. +@@ -18,11 +41,12 @@ WORKDIR /app + # and along with yarn.lock and the root package.json, that's enough to run yarn install. +-COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ ++COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ + RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz + +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + + # Then copy the rest of the backend bundle, along with any other files we might want. +-COPY packages/backend/dist/bundle.tar.gz app-config.yaml ./ ++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"] +diff --git a/packages/backend/README.md b/packages/backend/README.md +index 02426ef..3607b0a 100644 +--- a/packages/backend/README.md ++++ b/packages/backend/README.md +@@ -18,4 +18,2 @@ To run the example backend, first go to the project root and run + yarn install +-yarn tsc +-yarn build + ``` +@@ -27,7 +25,2 @@ After that, go to the `packages/backend` directory and run + ```bash +-AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x \ +-AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x \ +-AUTH_OAUTH2_CLIENT_ID=x AUTH_OAUTH2_CLIENT_SECRET=x \ +-AUTH_OAUTH2_AUTH_URL=x AUTH_OAUTH2_TOKEN_URL=x \ +-LOG_LEVEL=debug \ + yarn start +@@ -35,4 +28,4 @@ yarn start + +-Substitute `x` for actual values, or leave them as dummy values just to try out +-the backend without using the auth or sentry features. ++If you want to override any configuration locally, for example adding any secrets, ++you can do so in `app-config.local.yaml`. + +@@ -45,3 +38,3 @@ locations to the backend. These are places where the backend can find some + entity descriptor data to consume and serve. For more information, see +-[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog). ++[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). + +@@ -65,2 +58,2 @@ and + - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +-- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) ++- [Backstage Documentation](https://backstage.io/docs) +diff --git a/packages/backend/package.json b/packages/backend/package.json +index bf8390f..f224eaf 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,30 +6,33 @@ + "private": true, ++ "backstage": { ++ "role": "backend" ++ }, + "scripts": { +- "build": "backstage-cli backend:bundle", +- "build-image": "docker build ../.. -f Dockerfile --tag backstage", +- "start": "backstage-cli backend:dev", +- "lint": "backstage-cli lint", +- "test": "backstage-cli test", +- "clean": "backstage-cli clean", +- "migrate:create": "knex migrate:make -x ts" ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "lint": "backstage-cli package lint", ++ "test": "backstage-cli package test", ++ "clean": "backstage-cli package clean", ++ "build-image": "docker build ../.. -f Dockerfile --tag backstage" + }, + "dependencies": { +- "app": "0.0.0", +- "@backstage/backend-common": "^0.10.3", +- "@backstage/backend-tasks": "^0.1.3", +- "@backstage/catalog-model": "^0.9.9", +- "@backstage/catalog-client": "^0.5.4", +- "@backstage/config": "^0.1.12", +- "@backstage/plugin-app-backend": "^0.3.21", +- "@backstage/plugin-auth-backend": "^0.6.2", +- "@backstage/plugin-catalog-backend": "^0.20.0", +- "@backstage/plugin-permission-common": "^0.3.1", +- "@backstage/plugin-permission-node": "^0.3.0", +- "@backstage/plugin-proxy-backend": "^0.2.15", +- "@backstage/plugin-scaffolder-backend": "^0.15.20", +- "@backstage/plugin-search-backend": "^0.3.0", +- "@backstage/plugin-search-backend-node": "^0.4.4", +- "@backstage/plugin-techdocs-backend": "^0.12.3", +- "@gitbeaker/node": "^34.6.0", +- "@octokit/rest": "^18.5.3", ++ "@backstage/backend-common": "^0.19.9-next.1", ++ "@backstage/backend-tasks": "^0.5.12-next.1", ++ "@backstage/catalog-client": "^1.4.5", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/config": "^1.1.1", ++ "@backstage/plugin-app-backend": "^0.3.55-next.1", ++ "@backstage/plugin-auth-backend": "^0.20.0-next.1", ++ "@backstage/plugin-auth-node": "^0.4.1-next.1", ++ "@backstage/plugin-catalog-backend": "^1.15.0-next.1", ++ "@backstage/plugin-permission-common": "^0.7.9", ++ "@backstage/plugin-permission-node": "^0.7.18-next.1", ++ "@backstage/plugin-proxy-backend": "^0.4.5-next.1", ++ "@backstage/plugin-scaffolder-backend": "^1.19.0-next.1", ++ "@backstage/plugin-search-backend": "^1.4.7-next.1", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.16-next.1", ++ "@backstage/plugin-search-backend-node": "^1.2.11-next.1", ++ "@backstage/plugin-techdocs-backend": "^1.9.0-next.1", ++ "app": "link:../app", ++ "better-sqlite3": "^8.0.0", + "dockerode": "^3.3.1", +@@ -37,4 +40,3 @@ + "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "pg": "^8.3.0", + "winston": "^3.2.1" +@@ -42,6 +44,7 @@ + "devDependencies": { +- "@backstage/cli": "^0.11.0", ++ "@backstage/cli": "^0.24.0-next.1", + "@types/dockerode": "^3.3.0", + "@types/express": "^4.17.6", +- "@types/express-serve-static-core": "^4.17.5" ++ "@types/express-serve-static-core": "^4.17.5", ++ "@types/luxon": "^2.0.4" + }, +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 70bc66b..04c4ff9 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -17,3 +17,3 @@ import { + DatabaseManager, +- SingleHostDiscovery, ++ HostDiscovery, + UrlReaders, +@@ -32,2 +32,3 @@ import { PluginEnvironment } from './types'; + import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; + +@@ -36,7 +37,11 @@ function makeCreateEnv(config: Config) { + const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++ const discovery = HostDiscovery.fromConfig(config); + const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++ const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); + const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); ++ const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); ++ ++ const identity = DefaultIdentityClient.create({ ++ discovery, ++ }); + const permissions = ServerPermissionClient.fromConfig(config, { +@@ -63,2 +68,3 @@ function makeCreateEnv(config: Config) { + permissions, ++ identity, + }; +@@ -106,3 +112,3 @@ module.hot?.accept(); + main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); ++ console.error('Backend failed to start up', error); + process.exit(1); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +index 07fb04f..7c37f68 100644 +--- a/packages/backend/src/plugins/app.ts ++++ b/packages/backend/src/plugins/app.ts +@@ -4,9 +4,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + return await createRouter({ +- logger, +- config, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, + appPackageName: 'app', +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +index 5216510..77eb6aa 100644 +--- a/packages/backend/src/plugins/auth.ts ++++ b/packages/backend/src/plugins/auth.ts +@@ -1,2 +1,6 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; ++import { ++ createRouter, ++ providers, ++ defaultAuthProviderFactories, ++} from '@backstage/plugin-auth-backend'; + import { Router } from 'express'; +@@ -4,9 +8,47 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, database, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, ++ providerFactories: { ++ ...defaultAuthProviderFactories, ++ ++ // This replaces the default GitHub auth provider with a customized one. ++ // The `signIn` option enables sign-in for this provider, using the ++ // identity resolution logic that's provided in the `resolver` callback. ++ // ++ // This particular resolver makes all users share a single "guest" identity. ++ // It should only be used for testing and trying out Backstage. ++ // ++ // If you want to use a production ready resolver you can switch to ++ // the one that is commented out below, it looks up a user entity in the ++ // catalog using the GitHub username of the authenticated user. ++ // That resolver requires you to have user entities populated in the catalog, ++ // for example using https://backstage.io/docs/integrations/github/org ++ // ++ // There are other resolvers to choose from, and you can also create ++ // your own, see the auth documentation for more details: ++ // ++ // https://backstage.io/docs/auth/identity-resolver ++ github: providers.github.create({ ++ signIn: { ++ resolver(_, ctx) { ++ const userRef = 'user:default/guest'; // Must be a full entity reference ++ return ctx.issueToken({ ++ claims: { ++ sub: userRef, // The user's own identity ++ ent: [userRef], // A list of identities that the user claims ownership through ++ }, ++ }); ++ }, ++ // resolver: providers.github.resolvers.usernameMatchingUserEntityName(), ++ }, ++ }), ++ }, ++ }); + } +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +index 506f6d9..54ec393 100644 +--- a/packages/backend/src/plugins/proxy.ts ++++ b/packages/backend/src/plugins/proxy.ts +@@ -4,8 +4,10 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ }); + } +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +index 6be2e97..a12fee2 100644 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ b/packages/backend/src/plugins/scaffolder.ts +@@ -1,5 +1,3 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; + import { CatalogClient } from '@backstage/catalog-client'; + import { createRouter } from '@backstage/plugin-scaffolder-backend'; +-import Docker from 'dockerode'; + import { Router } from 'express'; +@@ -7,20 +5,17 @@ import type { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- database, +- reader, +- discovery, +-}: PluginEnvironment): Promise { +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- const catalogClient = new CatalogClient({ discoveryApi: discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ const catalogClient = new CatalogClient({ ++ discoveryApi: env.discovery, ++ }); + + return await createRouter({ +- containerRunner, +- logger, +- config, +- database, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ reader: env.reader, + catalogClient, +- reader, ++ identity: env.identity, ++ permissions: env.permissions, + }); +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +index f23b0c7..e9469dc 100644 +--- a/packages/backend/src/plugins/search.ts ++++ b/packages/backend/src/plugins/search.ts +@@ -7,14 +7,25 @@ import { + import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend'; ++import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend'; ++import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; ++import { Router } from 'express'; + +-export default async function createPlugin({ +- logger, +- discovery, +- config, +- tokenManager, +-}: PluginEnvironment) { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); ++ const searchEngine = new LunrSearchEngine({ ++ logger: env.logger, ++ }); ++ const indexBuilder = new IndexBuilder({ ++ logger: env.logger, ++ searchEngine, ++ }); ++ ++ const schedule = env.scheduler.createScheduledTaskRunner({ ++ frequency: { minutes: 10 }, ++ timeout: { minutes: 15 }, ++ // A 3 second delay gives the backend server a chance to initialize before ++ // any collators are executed, which may attempt requests against the API. ++ initialDelay: { seconds: 3 }, ++ }); + +@@ -23,6 +34,6 @@ export default async function createPlugin({ + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { +- discovery, +- tokenManager, ++ schedule, ++ factory: DefaultCatalogCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, + }), +@@ -32,7 +43,7 @@ export default async function createPlugin({ + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultTechDocsCollator.fromConfig(config, { +- discovery, +- logger, +- tokenManager, ++ schedule, ++ factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ logger: env.logger, ++ tokenManager: env.tokenManager, + }), +@@ -43,6 +54,4 @@ export default async function createPlugin({ + const { scheduler } = await indexBuilder.build(); ++ scheduler.start(); + +- // A 3 second delay gives the backend server a chance to initialize before +- // any collators are executed, which may attempt requests against the API. +- setTimeout(() => scheduler.start(), 3000); + useHotCleanup(module, () => scheduler.stop()); +@@ -51,3 +60,6 @@ export default async function createPlugin({ + engine: indexBuilder.getSearchEngine(), +- logger, ++ types: indexBuilder.getDocumentTypes(), ++ permissions: env.permissions, ++ config: env.config, ++ logger: env.logger, + }); +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +index 054c64d..be8bb0c 100644 +--- a/packages/backend/src/plugins/techdocs.ts ++++ b/packages/backend/src/plugins/techdocs.ts +@@ -11,13 +11,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +- cache, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, ++ const preparers = await Preparers.fromConfig(env.config, { ++ logger: env.logger, ++ reader: env.reader, + }); +@@ -29,4 +25,4 @@ export default async function createPlugin({ + // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, ++ const generators = await Generators.fromConfig(env.config, { ++ logger: env.logger, + containerRunner, +@@ -37,5 +33,5 @@ export default async function createPlugin({ + // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, ++ const publisher = await Publisher.fromConfig(env.config, { ++ logger: env.logger, ++ discovery: env.discovery, + }); +@@ -49,6 +45,6 @@ export default async function createPlugin({ + publisher, +- logger, +- config, +- discovery, +- cache, ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ cache: env.cache, + }); +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +index 0862b0e..9cd2c74 100644 +--- a/packages/backend/src/types.ts ++++ b/packages/backend/src/types.ts +@@ -10,3 +10,4 @@ import { + import { PluginTaskScheduler } from '@backstage/backend-tasks'; +-import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; ++import { PermissionEvaluator } from '@backstage/plugin-permission-common'; ++import { IdentityApi } from '@backstage/plugin-auth-node'; + +@@ -21,3 +22,4 @@ export type PluginEnvironment = { + scheduler: PluginTaskScheduler; +- permissions: PermissionAuthorizer; ++ permissions: PermissionEvaluator; ++ identity: IdentityApi; + }; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..37c7fb1 +--- /dev/null ++++ b/playwright.config.ts +@@ -0,0 +1,60 @@ ++/* ++ * Copyright 2023 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { defineConfig } from '@playwright/test'; ++import { generateProjects } from '@backstage/e2e-test-utils/playwright'; ++ ++/** ++ * See https://playwright.dev/docs/test-configuration. ++ */ ++export default defineConfig({ ++ timeout: 60_000, ++ ++ expect: { ++ timeout: 5_000, ++ }, ++ ++ // Run your local dev server before starting the tests ++ webServer: process.env.CI ++ ? [] ++ : [ ++ { ++ command: 'yarn start', ++ port: 3000, ++ reuseExistingServer: true, ++ timeout: 60_000, ++ }, ++ ], ++ ++ forbidOnly: !!process.env.CI, ++ ++ retries: process.env.CI ? 2 : 0, ++ ++ reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]], ++ ++ use: { ++ actionTimeout: 0, ++ baseURL: ++ process.env.PLAYWRIGHT_URL ?? ++ (process.env.CI ? 'http://localhost:7007' : 'http://localhost:3000'), ++ screenshot: 'only-on-failure', ++ trace: 'on-first-retry', ++ }, ++ ++ outputDir: 'node_modules/.cache/e2e-test-results', ++ ++ projects: generateProjects(), // Find all packages with e2e-test folders ++}); +diff --git a/plugins/README.md b/plugins/README.md +new file mode 100644 +index 0000000..d7865fd +--- /dev/null ++++ b/plugins/README.md +@@ -0,0 +1,9 @@ ++# The Plugins Folder ++ ++This is where your own plugins and their associated modules live, each in a ++separate folder of its own. ++ ++If you want to create a new plugin here, go to your project root directory, run ++the command `yarn new`, and follow the on-screen instructions. ++ ++You can also check out existing plugins on [the plugin marketplace](https://backstage.io/plugins)! +diff --git a/yarn.lock b/yarn.lock +new file mode 100644 +index 0000000..5ad7fe2 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1 @@ ++# intentionally left empty diff --git a/diffs/0.4.13..0.5.7-next.1.diff b/diffs/0.4.13..0.5.7-next.1.diff new file mode 100644 index 00000000..e4382309 --- /dev/null +++ b/diffs/0.4.13..0.5.7-next.1.diff @@ -0,0 +1,1789 @@ +diff --git a/.dockerignore b/.dockerignore +index 63c9c34..05edb62 100644 +--- a/.dockerignore ++++ b/.dockerignore +@@ -1,5 +1,8 @@ + .git ++.yarn/cache ++.yarn/install-state.gz + node_modules +-packages +-!packages/backend/dist ++packages/*/src ++packages/*/node_modules + plugins ++*.local.yaml +diff --git a/.eslintignore b/.eslintignore +new file mode 100644 +index 0000000..e5b1994 +--- /dev/null ++++ b/.eslintignore +@@ -0,0 +1 @@ ++playwright.config.ts +diff --git a/.gitignore b/.gitignore +index d16a8d3..fbf8139 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -17,2 +17,11 @@ node_modules/ + ++# Yarn 3 files ++.pnp.* ++.yarn/* ++!.yarn/patches ++!.yarn/plugins ++!.yarn/releases ++!.yarn/sdks ++!.yarn/versions ++ + # Node version directives +@@ -39 +48,7 @@ site + *-credentials.yaml ++ ++# vscode database functionality support files ++*.session.sql ++ ++# E2E test reports ++e2e-test-report/ +diff --git a/app-config.production.yaml b/app-config.production.yaml +index 5e36c23..8f0751c 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -1,3 +1,3 @@ + app: +- # Should be the same as backend.baseUrl when using the `app-backend` plugin ++ # Should be the same as backend.baseUrl when using the `app-backend` plugin. + baseUrl: http://localhost:7007 +@@ -5,4 +5,31 @@ app: + backend: ++ # Note that the baseUrl should be the URL that the browser and other clients ++ # should use when communicating with the backend, i.e. it needs to be ++ # reachable not just from within the backend host, but from all of your ++ # callers. When its value is "http://localhost:7007", it's strictly private ++ # and can't be reached by others. + baseUrl: http://localhost:7007 +- listen: +- port: 7007 ++ # The listener can also be expressed as a single : string. In this case we bind to ++ # all interfaces, the most permissive setting. The right value depends on your specific deployment. ++ listen: ':7007' ++ ++ # config options: https://node-postgres.com/api/client ++ database: ++ client: pg ++ connection: ++ host: ${POSTGRES_HOST} ++ port: ${POSTGRES_PORT} ++ user: ${POSTGRES_USER} ++ password: ${POSTGRES_PASSWORD} ++ # https://node-postgres.com/features/ssl ++ # you can set the sslmode configuration option via the `PGSSLMODE` environment variable ++ # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require) ++ # ssl: ++ # ca: # if you have a CA file and want to verify it you can uncomment this section ++ # $file: /ca/server.crt ++ ++catalog: ++ # Overrides the default list locations from app-config.yaml as these contain example data. ++ # See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details ++ # on how to get entities into the catalog. ++ locations: [] +diff --git a/app-config.yaml b/app-config.yaml +index 1a622a2..08d2abf 100644 +--- a/app-config.yaml ++++ b/app-config.yaml +@@ -9,3 +9,4 @@ backend: + # Used for enabling authentication, secret is shared by all backend plugins +- # See backend-to-backend-auth.md in the docs for information on the format ++ # See https://backstage.io/docs/auth/service-to-service-auth for ++ # information on the format + # auth: +@@ -16,2 +17,4 @@ backend: + port: 7007 ++ # Uncomment the following host directive to bind to specific interfaces ++ # host: 127.0.0.1 + csp: +@@ -22,9 +25,9 @@ backend: + origin: http://localhost:3000 +- methods: [GET, POST, PUT, DELETE] ++ methods: [GET, HEAD, PATCH, POST, PUT, DELETE] + credentials: true ++ # This is for local development only, it is not recommended to use this in production ++ # The production database configuration is stored in app-config.production.yaml + database: +- client: sqlite3 ++ client: better-sqlite3 + connection: ':memory:' +- cache: +- store: memory + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir +@@ -34,2 +37,4 @@ integrations: + - host: github.com ++ # This is a Personal Access Token or PAT from GitHub. You can find out how to generate this token, and more information ++ # about setting up the GitHub integration here: https://backstage.io/docs/getting-started/configuration#setting-up-a-github-integration + token: ${GITHUB_TOKEN} +@@ -41,5 +46,8 @@ integrations: + proxy: +- '/test': +- target: 'https://example.com' +- changeOrigin: true ++ ### Example for how to add a proxy endpoint for the frontend. ++ ### A typical reason to do this is to handle HTTPS and CORS for internal services. ++ # endpoints: ++ # '/test': ++ # target: 'https://example.com' ++ # changeOrigin: true + +@@ -64,41 +72,32 @@ scaffolder: + catalog: ++ import: ++ entityFilename: catalog-info.yaml ++ pullRequestBranchName: backstage-integration + rules: +- - allow: [Component, System, API, Group, User, Resource, Location] ++ - allow: [Component, System, API, Resource, Location] + locations: +- # Backstage example components +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-components.yaml ++ # Local example data, file locations are relative to the backend process, typically `packages/backend` ++ - type: file ++ target: ../../examples/entities.yaml + +- # Backstage example systems +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-systems.yaml +- +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml +- +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml +- +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.yaml +- +- # Backstage example templates +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/react-ssr-template/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/springboot-grpc-template/template.yaml ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml + rules: + - allow: [Template] +- - type: url +- target: https://github.com/spotify/cookiecutter-golang/blob/master/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/docs-template/template.yaml ++ ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml + rules: +- - allow: [Template] ++ - allow: [User, Group] ++ ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml ++ ++ ## Uncomment these lines to add an example org ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme-corp.yaml ++ # rules: ++ # - allow: [User, Group] +diff --git a/backstage.json b/backstage.json +index 5a69ad5..707af29 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "0.4.13" ++ "version": "1.20.0-next.1" + } +diff --git a/examples/entities.yaml b/examples/entities.yaml +new file mode 100644 +index 0000000..447e8b1 +--- /dev/null ++++ b/examples/entities.yaml +@@ -0,0 +1,41 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system ++apiVersion: backstage.io/v1alpha1 ++kind: System ++metadata: ++ name: examples ++spec: ++ owner: guests ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: example-website ++spec: ++ type: website ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ providesApis: [example-grpc-api] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api ++apiVersion: backstage.io/v1alpha1 ++kind: API ++metadata: ++ name: example-grpc-api ++spec: ++ type: grpc ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ definition: | ++ syntax = "proto3"; ++ ++ service Exampler { ++ rpc Example (ExampleMessage) returns (ExampleMessage) {}; ++ } ++ ++ message ExampleMessage { ++ string example = 1; ++ }; +diff --git a/examples/org.yaml b/examples/org.yaml +new file mode 100644 +index 0000000..a10e81f +--- /dev/null ++++ b/examples/org.yaml +@@ -0,0 +1,17 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user ++apiVersion: backstage.io/v1alpha1 ++kind: User ++metadata: ++ name: guest ++spec: ++ memberOf: [guests] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group ++apiVersion: backstage.io/v1alpha1 ++kind: Group ++metadata: ++ name: guests ++spec: ++ type: team ++ children: [] +diff --git a/examples/template/content/catalog-info.yaml b/examples/template/content/catalog-info.yaml +new file mode 100644 +index 0000000..d4ccca4 +--- /dev/null ++++ b/examples/template/content/catalog-info.yaml +@@ -0,0 +1,8 @@ ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: ${{ values.name | dump }} ++spec: ++ type: service ++ owner: user:guest ++ lifecycle: experimental +diff --git a/examples/template/content/index.js b/examples/template/content/index.js +new file mode 100644 +index 0000000..071ce5a +--- /dev/null ++++ b/examples/template/content/index.js +@@ -0,0 +1 @@ ++console.log('Hello from ${{ values.name }}!'); +diff --git a/examples/template/content/package.json b/examples/template/content/package.json +new file mode 100644 +index 0000000..86f968a +--- /dev/null ++++ b/examples/template/content/package.json +@@ -0,0 +1,5 @@ ++{ ++ "name": "${{ values.name }}", ++ "private": true, ++ "dependencies": {} ++} +diff --git a/examples/template/template.yaml b/examples/template/template.yaml +new file mode 100644 +index 0000000..33f262b +--- /dev/null ++++ b/examples/template/template.yaml +@@ -0,0 +1,74 @@ ++apiVersion: scaffolder.backstage.io/v1beta3 ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template ++kind: Template ++metadata: ++ name: example-nodejs-template ++ title: Example Node.js Template ++ description: An example template for the scaffolder that creates a simple Node.js service ++spec: ++ owner: user:guest ++ type: service ++ ++ # These parameters are used to generate the input form in the frontend, and are ++ # used to gather input data for the execution of the template. ++ parameters: ++ - title: Fill in some steps ++ required: ++ - name ++ properties: ++ name: ++ title: Name ++ type: string ++ description: Unique name of the component ++ ui:autofocus: true ++ ui:options: ++ rows: 5 ++ - title: Choose a location ++ required: ++ - repoUrl ++ properties: ++ repoUrl: ++ title: Repository Location ++ type: string ++ ui:field: RepoUrlPicker ++ ui:options: ++ allowedHosts: ++ - github.com ++ ++ # These steps are executed in the scaffolder backend, using data that we gathered ++ # via the parameters above. ++ steps: ++ # Each step executes an action, in this case one templates files into the working directory. ++ - id: fetch-base ++ name: Fetch Base ++ action: fetch:template ++ input: ++ url: ./content ++ values: ++ name: ${{ parameters.name }} ++ ++ # This step publishes the contents of the working directory to GitHub. ++ - id: publish ++ name: Publish ++ action: publish:github ++ input: ++ allowedHosts: ['github.com'] ++ description: This is ${{ parameters.name }} ++ repoUrl: ${{ parameters.repoUrl }} ++ ++ # The final step is to register our new component in the catalog. ++ - id: register ++ name: Register ++ action: catalog:register ++ input: ++ repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} ++ catalogInfoPath: '/catalog-info.yaml' ++ ++ # Outputs are displayed to the user after a successful execution of the template. ++ output: ++ links: ++ - title: Repository ++ url: ${{ steps['publish'].output.remoteUrl }} ++ - title: Open in catalog ++ icon: catalog ++ entityRef: ${{ steps['register'].output.entityRef }} +diff --git a/lerna.json b/lerna.json +index 322929d..529a62f 100644 +--- a/lerna.json ++++ b/lerna.json +@@ -3,4 +3,4 @@ + "npmClient": "yarn", +- "useWorkspaces": true, +- "version": "0.1.0" ++ "version": "0.1.0", ++ "$schema": "node_modules/lerna/schemas/lerna-schema.json" + } +diff --git a/package.json b/package.json +index 19a4f52..2364da8 100644 +--- a/package.json ++++ b/package.json +@@ -5,3 +5,3 @@ + "engines": { +- "node": "14 || 16" ++ "node": "18 || 20" + }, +@@ -11,3 +11,4 @@ + "start-backend": "yarn workspace backend start", +- "build": "lerna run build", ++ "build:backend": "yarn workspace backend build", ++ "build:all": "backstage-cli repo build --all", + "build-image": "yarn workspace backend build-image", +@@ -15,11 +16,11 @@ + "tsc:full": "tsc --skipLibCheck false --incremental false", +- "clean": "backstage-cli clean && lerna run clean", +- "diff": "lerna run diff --", +- "test": "backstage-cli test", +- "test:all": "lerna run test -- --coverage", +- "lint": "lerna run lint --since origin/master --", +- "lint:all": "lerna run lint --", ++ "clean": "backstage-cli repo clean", ++ "test": "backstage-cli repo test", ++ "test:all": "backstage-cli repo test --coverage", ++ "test:e2e": "playwright test", ++ "fix": "backstage-cli repo fix", ++ "lint": "backstage-cli repo lint --since origin/master", ++ "lint:all": "backstage-cli repo lint", + "prettier:check": "prettier --check .", +- "create-plugin": "backstage-cli create-plugin --scope internal", +- "remove-plugin": "backstage-cli remove-plugin" ++ "new": "backstage-cli new --scope internal" + }, +@@ -32,8 +33,15 @@ + "devDependencies": { +- "@backstage/cli": "^0.12.0-next.0", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/e2e-test-utils": "^0.1.0", ++ "@playwright/test": "^1.32.3", + "@spotify/prettier-config": "^12.0.0", +- "concurrently": "^6.0.0", +- "lerna": "^4.0.0", ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^9.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.5.4" ++ "typescript": "~5.2.0" ++ }, ++ "resolutions": { ++ "@types/react": "^17", ++ "@types/react-dom": "^17" + }, +diff --git a/packages/README.md b/packages/README.md +new file mode 100644 +index 0000000..6327fa0 +--- /dev/null ++++ b/packages/README.md +@@ -0,0 +1,9 @@ ++# The Packages Folder ++ ++This is where your own applications and centrally managed libraries live, each ++in a separate folder of its own. ++ ++From the start there's an `app` folder (for the frontend) and a `backend` folder ++(for the Node backend), but you can also add more modules in here that house ++your core additions and adaptations, such as themes, common React component ++libraries, utilities, and similar. +diff --git a/packages/app/.eslintignore b/packages/app/.eslintignore +new file mode 100644 +index 0000000..a48cf0d +--- /dev/null ++++ b/packages/app/.eslintignore +@@ -0,0 +1 @@ ++public +diff --git a/packages/app/.eslintrc.js b/packages/app/.eslintrc.js +index 13573ef..e2a53a6 100644 +--- a/packages/app/.eslintrc.js ++++ b/packages/app/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/app/cypress.json b/packages/app/cypress.json +deleted file mode 100644 +index 5de7ebf..0000000 +--- a/packages/app/cypress.json ++++ /dev/null +@@ -1,5 +0,0 @@ +-{ +- "baseUrl": "http://localhost:3001", +- "fixturesFolder": false, +- "pluginsFile": false +-} +diff --git a/packages/app/cypress/.eslintrc.json b/packages/app/cypress/.eslintrc.json +deleted file mode 100644 +index 2b3a458..0000000 +--- a/packages/app/cypress/.eslintrc.json ++++ /dev/null +@@ -1,21 +0,0 @@ +-{ +- "plugins": ["cypress"], +- "extends": ["plugin:cypress/recommended"], +- "rules": { +- "jest/expect-expect": [ +- "error", +- { +- "assertFunctionNames": ["expect", "cy.contains"] +- } +- ], +- "import/no-extraneous-dependencies": [ +- "error", +- { +- "devDependencies": true, +- "optionalDependencies": true, +- "peerDependencies": true, +- "bundledDependencies": true +- } +- ] +- } +-} +diff --git a/packages/app/cypress/integration/app.js b/packages/app/cypress/integration/app.js +deleted file mode 100644 +index 43fb2e3..0000000 +--- a/packages/app/cypress/integration/app.js ++++ /dev/null +@@ -1,6 +0,0 @@ +-describe('App', () => { +- it('should render the catalog', () => { +- cy.visit('/'); +- cy.contains('My Company Catalog'); +- }); +-}); +diff --git a/packages/app/e2e-tests/app.test.ts b/packages/app/e2e-tests/app.test.ts +new file mode 100644 +index 0000000..d45bc0d +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,23 @@ ++/* ++ * Copyright 2020 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { test, expect } from '@playwright/test'; ++ ++test('App should render the welcome page', async ({ page }) => { ++ await page.goto('/'); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 72c01d5..2100405 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,22 +5,38 @@ + "bundled": true, ++ "backstage": { ++ "role": "frontend" ++ }, ++ "scripts": { ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "clean": "backstage-cli package clean", ++ "test": "backstage-cli package test", ++ "lint": "backstage-cli package lint" ++ }, + "dependencies": { +- "@backstage/app-defaults": "^0.1.5-next.0", +- "@backstage/catalog-model": "^0.9.10-next.0", +- "@backstage/cli": "^0.12.0-next.0", +- "@backstage/core-app-api": "^0.5.0-next.0", +- "@backstage/core-components": "^0.8.5-next.0", +- "@backstage/core-plugin-api": "^0.6.0-next.0", +- "@backstage/integration-react": "^0.1.19-next.0", +- "@backstage/plugin-api-docs": "^0.6.23-next.0", +- "@backstage/plugin-catalog": "^0.7.9-next.0", +- "@backstage/plugin-catalog-import": "^0.7.10-next.0", +- "@backstage/plugin-catalog-react": "^0.6.12-next.0", +- "@backstage/plugin-github-actions": "^0.4.32-next.0", +- "@backstage/plugin-org": "^0.3.35-next.0", +- "@backstage/plugin-scaffolder": "^0.11.19-next.0", +- "@backstage/plugin-search": "^0.5.6-next.0", +- "@backstage/plugin-tech-radar": "^0.5.3-next.0", +- "@backstage/plugin-techdocs": "^0.12.15-next.0", +- "@backstage/plugin-user-settings": "^0.3.17-next.0", +- "@backstage/theme": "^0.2.14", ++ "@backstage/app-defaults": "^1.4.5-next.1", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/core-app-api": "^1.11.1-next.0", ++ "@backstage/core-components": "^0.13.8-next.1", ++ "@backstage/core-plugin-api": "^1.8.0-next.0", ++ "@backstage/integration-react": "^1.1.21-next.1", ++ "@backstage/plugin-api-docs": "^0.9.13-next.1", ++ "@backstage/plugin-catalog": "^1.15.0-next.1", ++ "@backstage/plugin-catalog-common": "^1.0.17", ++ "@backstage/plugin-catalog-graph": "^0.2.38-next.1", ++ "@backstage/plugin-catalog-import": "^0.10.2-next.1", ++ "@backstage/plugin-catalog-react": "^1.9.0-next.1", ++ "@backstage/plugin-github-actions": "^0.6.7-next.1", ++ "@backstage/plugin-org": "^0.6.16-next.1", ++ "@backstage/plugin-permission-react": "^0.4.17-next.0", ++ "@backstage/plugin-scaffolder": "^1.16.0-next.1", ++ "@backstage/plugin-search": "^1.4.2-next.1", ++ "@backstage/plugin-search-react": "^1.7.2-next.1", ++ "@backstage/plugin-tech-radar": "^0.6.10-next.1", ++ "@backstage/plugin-techdocs": "^1.8.1-next.1", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.2-next.1", ++ "@backstage/plugin-techdocs-react": "^1.1.13-next.1", ++ "@backstage/plugin-user-settings": "^0.7.12-next.1", ++ "@backstage/theme": "^0.4.4-next.0", + "@material-ui/core": "^4.12.2", +@@ -28,31 +44,17 @@ + "history": "^5.0.0", +- "react": "^16.13.1", +- "react-dom": "^16.13.1", +- "react-router": "6.0.0-beta.0", +- "react-router-dom": "6.0.0-beta.0", +- "react-use": "^15.3.3" ++ "react": "^17.0.2", ++ "react-dom": "^17.0.2", ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^0.2.3-next.0", ++ "@backstage/test-utils": "^1.4.5-next.0", ++ "@playwright/test": "^1.32.3", + "@testing-library/jest-dom": "^5.10.1", +- "@testing-library/react": "^10.4.1", +- "@testing-library/user-event": "^12.0.7", +- "@types/jest": "^26.0.7", +- "@types/node": "^14.14.32", ++ "@testing-library/react": "^12.1.3", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^8.0.0", + "@types/react-dom": "*", +- "cross-env": "^7.0.0", +- "cypress": "^7.3.0", +- "eslint-plugin-cypress": "^2.10.3", +- "start-server-and-test": "^1.10.11" +- }, +- "scripts": { +- "start": "backstage-cli app:serve", +- "build": "backstage-cli app:build", +- "clean": "backstage-cli clean", +- "test": "backstage-cli test", +- "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", +- "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", +- "lint": "backstage-cli lint", +- "cy:dev": "cypress open", +- "cy:run": "cypress run" ++ "cross-env": "^7.0.0" + }, +diff --git a/packages/app/public/index.html b/packages/app/public/index.html +index a936c73..fc758ee 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -10,3 +10,2 @@ + /> +- + +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 8a53583..8d62f29 100644 +--- a/packages/app/src/App.tsx ++++ b/packages/app/src/App.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { Navigate, Route } from 'react-router'; ++import { Navigate, Route } from 'react-router-dom'; + import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; +@@ -13,2 +13,3 @@ import { + import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; ++import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +@@ -16,3 +17,2 @@ import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -21,2 +21,4 @@ import { + } from '@backstage/plugin-techdocs'; ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; + import { UserSettingsPage } from '@backstage/plugin-user-settings'; +@@ -29,3 +31,6 @@ import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; + import { createApp } from '@backstage/app-defaults'; +-import { FlatRoutes } from '@backstage/core-app-api'; ++import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; ++import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; ++import { RequirePermission } from '@backstage/plugin-permission-react'; ++import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; + +@@ -37,5 +42,6 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); + bind(apiDocsPlugin.externalRoutes, { +- createComponent: scaffolderPlugin.routes.root, ++ registerApi: catalogImportPlugin.routes.importPage, + }); +@@ -43,2 +49,6 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, ++ }); ++ bind(orgPlugin.externalRoutes, { ++ catalogIndex: catalogPlugin.routes.catalogIndex, + }); +@@ -47,8 +57,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -60,5 +67,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -73,3 +82,10 @@ const routes = ( + /> +- } /> ++ ++ ++ ++ } ++ /> + }> +@@ -78,2 +94,3 @@ const routes = ( + } /> ++ } /> + +@@ -81,4 +98,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -88,5 +105,3 @@ const App = () => ( + +- ++ , + ); +- +-export default App; +diff --git a/packages/app/src/components/Root/LogoFull.tsx b/packages/app/src/components/Root/LogoFull.tsx +index c7b1c84..47e3b73 100644 +--- a/packages/app/src/components/Root/LogoFull.tsx ++++ b/packages/app/src/components/Root/LogoFull.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/LogoIcon.tsx b/packages/app/src/components/Root/LogoIcon.tsx +index 073cf6e..7eae8c7 100644 +--- a/packages/app/src/components/Root/LogoIcon.tsx ++++ b/packages/app/src/components/Root/LogoIcon.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx +index 7e98c7d..6768b48 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,19 +1,3 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- +-import React, { useContext, PropsWithChildren } from 'react'; +-import { Link, makeStyles } from '@material-ui/core'; ++import React, { PropsWithChildren } from 'react'; ++import { makeStyles } from '@material-ui/core'; + import HomeIcon from '@material-ui/icons/Home'; +@@ -25,3 +9,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -30,6 +13,3 @@ import { + } from '@backstage/plugin-user-settings'; +-import { +- SidebarSearchModal, +- SearchContextProvider, +-} from '@backstage/plugin-search'; ++import { SidebarSearchModal } from '@backstage/plugin-search'; + import { +@@ -37,3 +17,2 @@ import { + sidebarConfig, +- SidebarContext, + SidebarDivider, +@@ -44,2 +23,4 @@ import { + SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -65,3 +46,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -69,8 +50,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -86,5 +62,3 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + } to="/search"> +- +- +- {' '} ++ + +diff --git a/packages/app/src/components/Root/index.ts b/packages/app/src/components/Root/index.ts +index dff706f..3528881 100644 +--- a/packages/app/src/components/Root/index.ts ++++ b/packages/app/src/components/Root/index.ts +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + export { Root } from './Root'; +diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx +index d3b4b78..6722ea2 100644 +--- a/packages/app/src/components/catalog/EntityPage.tsx ++++ b/packages/app/src/components/catalog/EntityPage.tsx +@@ -1,16 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ + import React from 'react'; +@@ -29,3 +14,2 @@ import { + EntityDependsOnResourcesCard, +- EntitySystemDiagramCard, + EntityHasComponentsCard, +@@ -43,2 +27,4 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +@@ -56,2 +42,27 @@ import { EntityTechdocsContent } from '@backstage/plugin-techdocs'; + import { EmptyState } from '@backstage/core-components'; ++import { ++ Direction, ++ EntityCatalogGraphCard, ++} from '@backstage/plugin-catalog-graph'; ++import { ++ RELATION_API_CONSUMED_BY, ++ RELATION_API_PROVIDED_BY, ++ RELATION_CONSUMES_API, ++ RELATION_DEPENDENCY_OF, ++ RELATION_DEPENDS_ON, ++ RELATION_HAS_PART, ++ RELATION_PART_OF, ++ RELATION_PROVIDES_API, ++} from '@backstage/catalog-model'; ++ ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); + +@@ -94,2 +105,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -110,2 +129,6 @@ const overviewContent = ( + ++ ++ ++ ++ + +@@ -152,3 +175,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -179,3 +202,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -198,3 +221,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -225,2 +248,5 @@ const apiPage = ( + ++ ++ ++ + +@@ -292,3 +318,9 @@ const systemPage = ( + +- ++ ++ ++ ++ ++ ++ ++ + +@@ -304,3 +336,19 @@ const systemPage = ( + +- ++ + +@@ -317,2 +365,5 @@ const domainPage = ( + ++ ++ ++ + +diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx +index cf380b6..1788dde 100644 +--- a/packages/app/src/components/search/SearchPage.tsx ++++ b/packages/app/src/components/search/SearchPage.tsx +@@ -1,7 +1,12 @@ + import React from 'react'; +-import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; ++import { makeStyles, Theme, Grid, Paper } from '@material-ui/core'; + +-import { CatalogResultListItem } from '@backstage/plugin-catalog'; +-import { DocsResultListItem } from '@backstage/plugin-techdocs'; ++import { CatalogSearchResultListItem } from '@backstage/plugin-catalog'; ++import { ++ catalogApiRef, ++ CATALOG_FILTER_EXISTS, ++} from '@backstage/plugin-catalog-react'; ++import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; + ++import { SearchType } from '@backstage/plugin-search'; + import { +@@ -10,5 +15,5 @@ import { + SearchResult, +- SearchType, +- DefaultResultListItem, +-} from '@backstage/plugin-search'; ++ SearchPagination, ++ useSearch, ++} from '@backstage/plugin-search-react'; + import { +@@ -20,2 +25,3 @@ import { + } from '@backstage/core-components'; ++import { useApi } from '@backstage/core-plugin-api'; + +@@ -38,2 +44,4 @@ const SearchPage = () => { + const classes = useStyles(); ++ const { types } = useSearch(); ++ const catalogApi = useApi(catalogApiRef); + +@@ -67,4 +75,26 @@ const SearchPage = () => { + ++ {types.includes('techdocs') && ( ++ { ++ // Return a list of entities which are documented. ++ const { items } = await catalogApi.getEntities({ ++ fields: ['metadata.name'], ++ filter: { ++ 'metadata.annotations.backstage.io/techdocs-ref': ++ CATALOG_FILTER_EXISTS, ++ }, ++ }); ++ ++ const names = items.map(entity => entity.metadata.name); ++ names.sort(); ++ return names; ++ }} ++ /> ++ )} + { + className={classes.filter} ++ label="Lifecycle" + name="lifecycle" +@@ -80,32 +111,6 @@ const SearchPage = () => { + ++ + +- {({ results }) => ( +- +- {results.map(({ type, document }) => { +- switch (type) { +- case 'software-catalog': +- return ( +- +- ); +- case 'techdocs': +- return ( +- +- ); +- default: +- return ( +- +- ); +- } +- })} +- +- )} ++ } /> ++ } /> + +diff --git a/packages/backend/.eslintrc.js b/packages/backend/.eslintrc.js +index 16a033d..e2a53a6 100644 +--- a/packages/backend/.eslintrc.js ++++ b/packages/backend/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint.backend')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index 31231a3..18548e9 100644 +--- a/packages/backend/Dockerfile ++++ b/packages/backend/Dockerfile +@@ -7,3 +7,3 @@ + # yarn tsc +-# yarn build ++# yarn build:backend + # +@@ -11,6 +11,29 @@ + +-FROM node:14-buster-slim ++FROM node:18-bookworm-slim + ++# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends python3 g++ build-essential && \ ++ yarn config set python /usr/bin/python3 ++ ++# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, ++# in which case you should also move better-sqlite3 to "devDependencies" in package.json. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends libsqlite3-dev ++ ++# From here on we use the least-privileged `node` user to run the backend. ++USER node ++ ++# This should create the app dir as `node`. ++# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. ++# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. + WORKDIR /app + ++# This switches many Node.js dependencies to production mode. ++ENV NODE_ENV production ++ + # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. +@@ -18,11 +41,12 @@ WORKDIR /app + # and along with yarn.lock and the root package.json, that's enough to run yarn install. +-COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ ++COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ + RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz + +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + + # Then copy the rest of the backend bundle, along with any other files we might want. +-COPY packages/backend/dist/bundle.tar.gz app-config.yaml ./ ++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"] +diff --git a/packages/backend/README.md b/packages/backend/README.md +index 02426ef..3607b0a 100644 +--- a/packages/backend/README.md ++++ b/packages/backend/README.md +@@ -18,4 +18,2 @@ To run the example backend, first go to the project root and run + yarn install +-yarn tsc +-yarn build + ``` +@@ -27,7 +25,2 @@ After that, go to the `packages/backend` directory and run + ```bash +-AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x \ +-AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x \ +-AUTH_OAUTH2_CLIENT_ID=x AUTH_OAUTH2_CLIENT_SECRET=x \ +-AUTH_OAUTH2_AUTH_URL=x AUTH_OAUTH2_TOKEN_URL=x \ +-LOG_LEVEL=debug \ + yarn start +@@ -35,4 +28,4 @@ yarn start + +-Substitute `x` for actual values, or leave them as dummy values just to try out +-the backend without using the auth or sentry features. ++If you want to override any configuration locally, for example adding any secrets, ++you can do so in `app-config.local.yaml`. + +@@ -45,3 +38,3 @@ locations to the backend. These are places where the backend can find some + entity descriptor data to consume and serve. For more information, see +-[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog). ++[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). + +@@ -65,2 +58,2 @@ and + - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +-- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) ++- [Backstage Documentation](https://backstage.io/docs) +diff --git a/packages/backend/package.json b/packages/backend/package.json +index 63c5c0d..f224eaf 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,30 +6,33 @@ + "private": true, ++ "backstage": { ++ "role": "backend" ++ }, + "scripts": { +- "build": "backstage-cli backend:bundle", +- "build-image": "docker build ../.. -f Dockerfile --tag backstage", +- "start": "backstage-cli backend:dev", +- "lint": "backstage-cli lint", +- "test": "backstage-cli test", +- "clean": "backstage-cli clean", +- "migrate:create": "knex migrate:make -x ts" ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "lint": "backstage-cli package lint", ++ "test": "backstage-cli package test", ++ "clean": "backstage-cli package clean", ++ "build-image": "docker build ../.. -f Dockerfile --tag backstage" + }, + "dependencies": { +- "app": "0.0.0", +- "@backstage/backend-common": "^0.10.4-next.0", +- "@backstage/backend-tasks": "^0.1.4-next.0", +- "@backstage/catalog-model": "^0.9.10-next.0", +- "@backstage/catalog-client": "^0.5.5-next.0", +- "@backstage/config": "^0.1.13-next.0", +- "@backstage/plugin-app-backend": "^0.3.22-next.0", +- "@backstage/plugin-auth-backend": "^0.7.0-next.0", +- "@backstage/plugin-catalog-backend": "^0.21.0-next.0", +- "@backstage/plugin-permission-common": "^0.4.0-next.0", +- "@backstage/plugin-permission-node": "^0.4.0-next.0", +- "@backstage/plugin-proxy-backend": "^0.2.16-next.0", +- "@backstage/plugin-scaffolder-backend": "^0.15.21-next.0", +- "@backstage/plugin-search-backend": "^0.3.1-next.0", +- "@backstage/plugin-search-backend-node": "^0.4.4", +- "@backstage/plugin-techdocs-backend": "^0.12.4-next.0", +- "@gitbeaker/node": "^34.6.0", +- "@octokit/rest": "^18.5.3", ++ "@backstage/backend-common": "^0.19.9-next.1", ++ "@backstage/backend-tasks": "^0.5.12-next.1", ++ "@backstage/catalog-client": "^1.4.5", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/config": "^1.1.1", ++ "@backstage/plugin-app-backend": "^0.3.55-next.1", ++ "@backstage/plugin-auth-backend": "^0.20.0-next.1", ++ "@backstage/plugin-auth-node": "^0.4.1-next.1", ++ "@backstage/plugin-catalog-backend": "^1.15.0-next.1", ++ "@backstage/plugin-permission-common": "^0.7.9", ++ "@backstage/plugin-permission-node": "^0.7.18-next.1", ++ "@backstage/plugin-proxy-backend": "^0.4.5-next.1", ++ "@backstage/plugin-scaffolder-backend": "^1.19.0-next.1", ++ "@backstage/plugin-search-backend": "^1.4.7-next.1", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.16-next.1", ++ "@backstage/plugin-search-backend-node": "^1.2.11-next.1", ++ "@backstage/plugin-techdocs-backend": "^1.9.0-next.1", ++ "app": "link:../app", ++ "better-sqlite3": "^8.0.0", + "dockerode": "^3.3.1", +@@ -37,4 +40,3 @@ + "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "pg": "^8.3.0", + "winston": "^3.2.1" +@@ -42,6 +44,7 @@ + "devDependencies": { +- "@backstage/cli": "^0.12.0-next.0", ++ "@backstage/cli": "^0.24.0-next.1", + "@types/dockerode": "^3.3.0", + "@types/express": "^4.17.6", +- "@types/express-serve-static-core": "^4.17.5" ++ "@types/express-serve-static-core": "^4.17.5", ++ "@types/luxon": "^2.0.4" + }, +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 70bc66b..04c4ff9 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -17,3 +17,3 @@ import { + DatabaseManager, +- SingleHostDiscovery, ++ HostDiscovery, + UrlReaders, +@@ -32,2 +32,3 @@ import { PluginEnvironment } from './types'; + import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; + +@@ -36,7 +37,11 @@ function makeCreateEnv(config: Config) { + const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++ const discovery = HostDiscovery.fromConfig(config); + const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++ const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); + const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); ++ const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); ++ ++ const identity = DefaultIdentityClient.create({ ++ discovery, ++ }); + const permissions = ServerPermissionClient.fromConfig(config, { +@@ -63,2 +68,3 @@ function makeCreateEnv(config: Config) { + permissions, ++ identity, + }; +@@ -106,3 +112,3 @@ module.hot?.accept(); + main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); ++ console.error('Backend failed to start up', error); + process.exit(1); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +index 14e19a1..7c37f68 100644 +--- a/packages/backend/src/plugins/app.ts ++++ b/packages/backend/src/plugins/app.ts +@@ -4,11 +4,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- database, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + return await createRouter({ +- logger, +- config, +- database, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, + appPackageName: 'app', +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +index 5216510..77eb6aa 100644 +--- a/packages/backend/src/plugins/auth.ts ++++ b/packages/backend/src/plugins/auth.ts +@@ -1,2 +1,6 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; ++import { ++ createRouter, ++ providers, ++ defaultAuthProviderFactories, ++} from '@backstage/plugin-auth-backend'; + import { Router } from 'express'; +@@ -4,9 +8,47 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, database, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, ++ providerFactories: { ++ ...defaultAuthProviderFactories, ++ ++ // This replaces the default GitHub auth provider with a customized one. ++ // The `signIn` option enables sign-in for this provider, using the ++ // identity resolution logic that's provided in the `resolver` callback. ++ // ++ // This particular resolver makes all users share a single "guest" identity. ++ // It should only be used for testing and trying out Backstage. ++ // ++ // If you want to use a production ready resolver you can switch to ++ // the one that is commented out below, it looks up a user entity in the ++ // catalog using the GitHub username of the authenticated user. ++ // That resolver requires you to have user entities populated in the catalog, ++ // for example using https://backstage.io/docs/integrations/github/org ++ // ++ // There are other resolvers to choose from, and you can also create ++ // your own, see the auth documentation for more details: ++ // ++ // https://backstage.io/docs/auth/identity-resolver ++ github: providers.github.create({ ++ signIn: { ++ resolver(_, ctx) { ++ const userRef = 'user:default/guest'; // Must be a full entity reference ++ return ctx.issueToken({ ++ claims: { ++ sub: userRef, // The user's own identity ++ ent: [userRef], // A list of identities that the user claims ownership through ++ }, ++ }); ++ }, ++ // resolver: providers.github.resolvers.usernameMatchingUserEntityName(), ++ }, ++ }), ++ }, ++ }); + } +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +index 506f6d9..54ec393 100644 +--- a/packages/backend/src/plugins/proxy.ts ++++ b/packages/backend/src/plugins/proxy.ts +@@ -4,8 +4,10 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ }); + } +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +index 6be2e97..a12fee2 100644 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ b/packages/backend/src/plugins/scaffolder.ts +@@ -1,5 +1,3 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; + import { CatalogClient } from '@backstage/catalog-client'; + import { createRouter } from '@backstage/plugin-scaffolder-backend'; +-import Docker from 'dockerode'; + import { Router } from 'express'; +@@ -7,20 +5,17 @@ import type { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- database, +- reader, +- discovery, +-}: PluginEnvironment): Promise { +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- const catalogClient = new CatalogClient({ discoveryApi: discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ const catalogClient = new CatalogClient({ ++ discoveryApi: env.discovery, ++ }); + + return await createRouter({ +- containerRunner, +- logger, +- config, +- database, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ reader: env.reader, + catalogClient, +- reader, ++ identity: env.identity, ++ permissions: env.permissions, + }); +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +index f23b0c7..e9469dc 100644 +--- a/packages/backend/src/plugins/search.ts ++++ b/packages/backend/src/plugins/search.ts +@@ -7,14 +7,25 @@ import { + import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend'; ++import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend'; ++import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; ++import { Router } from 'express'; + +-export default async function createPlugin({ +- logger, +- discovery, +- config, +- tokenManager, +-}: PluginEnvironment) { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); ++ const searchEngine = new LunrSearchEngine({ ++ logger: env.logger, ++ }); ++ const indexBuilder = new IndexBuilder({ ++ logger: env.logger, ++ searchEngine, ++ }); ++ ++ const schedule = env.scheduler.createScheduledTaskRunner({ ++ frequency: { minutes: 10 }, ++ timeout: { minutes: 15 }, ++ // A 3 second delay gives the backend server a chance to initialize before ++ // any collators are executed, which may attempt requests against the API. ++ initialDelay: { seconds: 3 }, ++ }); + +@@ -23,6 +34,6 @@ export default async function createPlugin({ + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { +- discovery, +- tokenManager, ++ schedule, ++ factory: DefaultCatalogCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, + }), +@@ -32,7 +43,7 @@ export default async function createPlugin({ + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultTechDocsCollator.fromConfig(config, { +- discovery, +- logger, +- tokenManager, ++ schedule, ++ factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ logger: env.logger, ++ tokenManager: env.tokenManager, + }), +@@ -43,6 +54,4 @@ export default async function createPlugin({ + const { scheduler } = await indexBuilder.build(); ++ scheduler.start(); + +- // A 3 second delay gives the backend server a chance to initialize before +- // any collators are executed, which may attempt requests against the API. +- setTimeout(() => scheduler.start(), 3000); + useHotCleanup(module, () => scheduler.stop()); +@@ -51,3 +60,6 @@ export default async function createPlugin({ + engine: indexBuilder.getSearchEngine(), +- logger, ++ types: indexBuilder.getDocumentTypes(), ++ permissions: env.permissions, ++ config: env.config, ++ logger: env.logger, + }); +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +index 054c64d..be8bb0c 100644 +--- a/packages/backend/src/plugins/techdocs.ts ++++ b/packages/backend/src/plugins/techdocs.ts +@@ -11,13 +11,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +- cache, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, ++ const preparers = await Preparers.fromConfig(env.config, { ++ logger: env.logger, ++ reader: env.reader, + }); +@@ -29,4 +25,4 @@ export default async function createPlugin({ + // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, ++ const generators = await Generators.fromConfig(env.config, { ++ logger: env.logger, + containerRunner, +@@ -37,5 +33,5 @@ export default async function createPlugin({ + // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, ++ const publisher = await Publisher.fromConfig(env.config, { ++ logger: env.logger, ++ discovery: env.discovery, + }); +@@ -49,6 +45,6 @@ export default async function createPlugin({ + publisher, +- logger, +- config, +- discovery, +- cache, ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ cache: env.cache, + }); +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +index 0862b0e..9cd2c74 100644 +--- a/packages/backend/src/types.ts ++++ b/packages/backend/src/types.ts +@@ -10,3 +10,4 @@ import { + import { PluginTaskScheduler } from '@backstage/backend-tasks'; +-import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; ++import { PermissionEvaluator } from '@backstage/plugin-permission-common'; ++import { IdentityApi } from '@backstage/plugin-auth-node'; + +@@ -21,3 +22,4 @@ export type PluginEnvironment = { + scheduler: PluginTaskScheduler; +- permissions: PermissionAuthorizer; ++ permissions: PermissionEvaluator; ++ identity: IdentityApi; + }; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..37c7fb1 +--- /dev/null ++++ b/playwright.config.ts +@@ -0,0 +1,60 @@ ++/* ++ * Copyright 2023 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { defineConfig } from '@playwright/test'; ++import { generateProjects } from '@backstage/e2e-test-utils/playwright'; ++ ++/** ++ * See https://playwright.dev/docs/test-configuration. ++ */ ++export default defineConfig({ ++ timeout: 60_000, ++ ++ expect: { ++ timeout: 5_000, ++ }, ++ ++ // Run your local dev server before starting the tests ++ webServer: process.env.CI ++ ? [] ++ : [ ++ { ++ command: 'yarn start', ++ port: 3000, ++ reuseExistingServer: true, ++ timeout: 60_000, ++ }, ++ ], ++ ++ forbidOnly: !!process.env.CI, ++ ++ retries: process.env.CI ? 2 : 0, ++ ++ reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]], ++ ++ use: { ++ actionTimeout: 0, ++ baseURL: ++ process.env.PLAYWRIGHT_URL ?? ++ (process.env.CI ? 'http://localhost:7007' : 'http://localhost:3000'), ++ screenshot: 'only-on-failure', ++ trace: 'on-first-retry', ++ }, ++ ++ outputDir: 'node_modules/.cache/e2e-test-results', ++ ++ projects: generateProjects(), // Find all packages with e2e-test folders ++}); +diff --git a/plugins/README.md b/plugins/README.md +new file mode 100644 +index 0000000..d7865fd +--- /dev/null ++++ b/plugins/README.md +@@ -0,0 +1,9 @@ ++# The Plugins Folder ++ ++This is where your own plugins and their associated modules live, each in a ++separate folder of its own. ++ ++If you want to create a new plugin here, go to your project root directory, run ++the command `yarn new`, and follow the on-screen instructions. ++ ++You can also check out existing plugins on [the plugin marketplace](https://backstage.io/plugins)! +diff --git a/yarn.lock b/yarn.lock +new file mode 100644 +index 0000000..5ad7fe2 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1 @@ ++# intentionally left empty diff --git a/diffs/0.4.14..0.5.7-next.1.diff b/diffs/0.4.14..0.5.7-next.1.diff new file mode 100644 index 00000000..bb162cd3 --- /dev/null +++ b/diffs/0.4.14..0.5.7-next.1.diff @@ -0,0 +1,1774 @@ +diff --git a/.dockerignore b/.dockerignore +index 63c9c34..05edb62 100644 +--- a/.dockerignore ++++ b/.dockerignore +@@ -1,5 +1,8 @@ + .git ++.yarn/cache ++.yarn/install-state.gz + node_modules +-packages +-!packages/backend/dist ++packages/*/src ++packages/*/node_modules + plugins ++*.local.yaml +diff --git a/.eslintignore b/.eslintignore +new file mode 100644 +index 0000000..e5b1994 +--- /dev/null ++++ b/.eslintignore +@@ -0,0 +1 @@ ++playwright.config.ts +diff --git a/.gitignore b/.gitignore +index d16a8d3..fbf8139 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -17,2 +17,11 @@ node_modules/ + ++# Yarn 3 files ++.pnp.* ++.yarn/* ++!.yarn/patches ++!.yarn/plugins ++!.yarn/releases ++!.yarn/sdks ++!.yarn/versions ++ + # Node version directives +@@ -39 +48,7 @@ site + *-credentials.yaml ++ ++# vscode database functionality support files ++*.session.sql ++ ++# E2E test reports ++e2e-test-report/ +diff --git a/app-config.production.yaml b/app-config.production.yaml +index 5e36c23..8f0751c 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -1,3 +1,3 @@ + app: +- # Should be the same as backend.baseUrl when using the `app-backend` plugin ++ # Should be the same as backend.baseUrl when using the `app-backend` plugin. + baseUrl: http://localhost:7007 +@@ -5,4 +5,31 @@ app: + backend: ++ # Note that the baseUrl should be the URL that the browser and other clients ++ # should use when communicating with the backend, i.e. it needs to be ++ # reachable not just from within the backend host, but from all of your ++ # callers. When its value is "http://localhost:7007", it's strictly private ++ # and can't be reached by others. + baseUrl: http://localhost:7007 +- listen: +- port: 7007 ++ # The listener can also be expressed as a single : string. In this case we bind to ++ # all interfaces, the most permissive setting. The right value depends on your specific deployment. ++ listen: ':7007' ++ ++ # config options: https://node-postgres.com/api/client ++ database: ++ client: pg ++ connection: ++ host: ${POSTGRES_HOST} ++ port: ${POSTGRES_PORT} ++ user: ${POSTGRES_USER} ++ password: ${POSTGRES_PASSWORD} ++ # https://node-postgres.com/features/ssl ++ # you can set the sslmode configuration option via the `PGSSLMODE` environment variable ++ # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require) ++ # ssl: ++ # ca: # if you have a CA file and want to verify it you can uncomment this section ++ # $file: /ca/server.crt ++ ++catalog: ++ # Overrides the default list locations from app-config.yaml as these contain example data. ++ # See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details ++ # on how to get entities into the catalog. ++ locations: [] +diff --git a/app-config.yaml b/app-config.yaml +index 1a622a2..08d2abf 100644 +--- a/app-config.yaml ++++ b/app-config.yaml +@@ -9,3 +9,4 @@ backend: + # Used for enabling authentication, secret is shared by all backend plugins +- # See backend-to-backend-auth.md in the docs for information on the format ++ # See https://backstage.io/docs/auth/service-to-service-auth for ++ # information on the format + # auth: +@@ -16,2 +17,4 @@ backend: + port: 7007 ++ # Uncomment the following host directive to bind to specific interfaces ++ # host: 127.0.0.1 + csp: +@@ -22,9 +25,9 @@ backend: + origin: http://localhost:3000 +- methods: [GET, POST, PUT, DELETE] ++ methods: [GET, HEAD, PATCH, POST, PUT, DELETE] + credentials: true ++ # This is for local development only, it is not recommended to use this in production ++ # The production database configuration is stored in app-config.production.yaml + database: +- client: sqlite3 ++ client: better-sqlite3 + connection: ':memory:' +- cache: +- store: memory + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir +@@ -34,2 +37,4 @@ integrations: + - host: github.com ++ # This is a Personal Access Token or PAT from GitHub. You can find out how to generate this token, and more information ++ # about setting up the GitHub integration here: https://backstage.io/docs/getting-started/configuration#setting-up-a-github-integration + token: ${GITHUB_TOKEN} +@@ -41,5 +46,8 @@ integrations: + proxy: +- '/test': +- target: 'https://example.com' +- changeOrigin: true ++ ### Example for how to add a proxy endpoint for the frontend. ++ ### A typical reason to do this is to handle HTTPS and CORS for internal services. ++ # endpoints: ++ # '/test': ++ # target: 'https://example.com' ++ # changeOrigin: true + +@@ -64,41 +72,32 @@ scaffolder: + catalog: ++ import: ++ entityFilename: catalog-info.yaml ++ pullRequestBranchName: backstage-integration + rules: +- - allow: [Component, System, API, Group, User, Resource, Location] ++ - allow: [Component, System, API, Resource, Location] + locations: +- # Backstage example components +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-components.yaml ++ # Local example data, file locations are relative to the backend process, typically `packages/backend` ++ - type: file ++ target: ../../examples/entities.yaml + +- # Backstage example systems +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-systems.yaml +- +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml +- +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml +- +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.yaml +- +- # Backstage example templates +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/react-ssr-template/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/springboot-grpc-template/template.yaml ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml + rules: + - allow: [Template] +- - type: url +- target: https://github.com/spotify/cookiecutter-golang/blob/master/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/docs-template/template.yaml ++ ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml + rules: +- - allow: [Template] ++ - allow: [User, Group] ++ ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml ++ ++ ## Uncomment these lines to add an example org ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme-corp.yaml ++ # rules: ++ # - allow: [User, Group] +diff --git a/backstage.json b/backstage.json +index e91ab70..707af29 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "0.4.14" ++ "version": "1.20.0-next.1" + } +diff --git a/examples/entities.yaml b/examples/entities.yaml +new file mode 100644 +index 0000000..447e8b1 +--- /dev/null ++++ b/examples/entities.yaml +@@ -0,0 +1,41 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system ++apiVersion: backstage.io/v1alpha1 ++kind: System ++metadata: ++ name: examples ++spec: ++ owner: guests ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: example-website ++spec: ++ type: website ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ providesApis: [example-grpc-api] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api ++apiVersion: backstage.io/v1alpha1 ++kind: API ++metadata: ++ name: example-grpc-api ++spec: ++ type: grpc ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ definition: | ++ syntax = "proto3"; ++ ++ service Exampler { ++ rpc Example (ExampleMessage) returns (ExampleMessage) {}; ++ } ++ ++ message ExampleMessage { ++ string example = 1; ++ }; +diff --git a/examples/org.yaml b/examples/org.yaml +new file mode 100644 +index 0000000..a10e81f +--- /dev/null ++++ b/examples/org.yaml +@@ -0,0 +1,17 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user ++apiVersion: backstage.io/v1alpha1 ++kind: User ++metadata: ++ name: guest ++spec: ++ memberOf: [guests] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group ++apiVersion: backstage.io/v1alpha1 ++kind: Group ++metadata: ++ name: guests ++spec: ++ type: team ++ children: [] +diff --git a/examples/template/content/catalog-info.yaml b/examples/template/content/catalog-info.yaml +new file mode 100644 +index 0000000..d4ccca4 +--- /dev/null ++++ b/examples/template/content/catalog-info.yaml +@@ -0,0 +1,8 @@ ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: ${{ values.name | dump }} ++spec: ++ type: service ++ owner: user:guest ++ lifecycle: experimental +diff --git a/examples/template/content/index.js b/examples/template/content/index.js +new file mode 100644 +index 0000000..071ce5a +--- /dev/null ++++ b/examples/template/content/index.js +@@ -0,0 +1 @@ ++console.log('Hello from ${{ values.name }}!'); +diff --git a/examples/template/content/package.json b/examples/template/content/package.json +new file mode 100644 +index 0000000..86f968a +--- /dev/null ++++ b/examples/template/content/package.json +@@ -0,0 +1,5 @@ ++{ ++ "name": "${{ values.name }}", ++ "private": true, ++ "dependencies": {} ++} +diff --git a/examples/template/template.yaml b/examples/template/template.yaml +new file mode 100644 +index 0000000..33f262b +--- /dev/null ++++ b/examples/template/template.yaml +@@ -0,0 +1,74 @@ ++apiVersion: scaffolder.backstage.io/v1beta3 ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template ++kind: Template ++metadata: ++ name: example-nodejs-template ++ title: Example Node.js Template ++ description: An example template for the scaffolder that creates a simple Node.js service ++spec: ++ owner: user:guest ++ type: service ++ ++ # These parameters are used to generate the input form in the frontend, and are ++ # used to gather input data for the execution of the template. ++ parameters: ++ - title: Fill in some steps ++ required: ++ - name ++ properties: ++ name: ++ title: Name ++ type: string ++ description: Unique name of the component ++ ui:autofocus: true ++ ui:options: ++ rows: 5 ++ - title: Choose a location ++ required: ++ - repoUrl ++ properties: ++ repoUrl: ++ title: Repository Location ++ type: string ++ ui:field: RepoUrlPicker ++ ui:options: ++ allowedHosts: ++ - github.com ++ ++ # These steps are executed in the scaffolder backend, using data that we gathered ++ # via the parameters above. ++ steps: ++ # Each step executes an action, in this case one templates files into the working directory. ++ - id: fetch-base ++ name: Fetch Base ++ action: fetch:template ++ input: ++ url: ./content ++ values: ++ name: ${{ parameters.name }} ++ ++ # This step publishes the contents of the working directory to GitHub. ++ - id: publish ++ name: Publish ++ action: publish:github ++ input: ++ allowedHosts: ['github.com'] ++ description: This is ${{ parameters.name }} ++ repoUrl: ${{ parameters.repoUrl }} ++ ++ # The final step is to register our new component in the catalog. ++ - id: register ++ name: Register ++ action: catalog:register ++ input: ++ repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} ++ catalogInfoPath: '/catalog-info.yaml' ++ ++ # Outputs are displayed to the user after a successful execution of the template. ++ output: ++ links: ++ - title: Repository ++ url: ${{ steps['publish'].output.remoteUrl }} ++ - title: Open in catalog ++ icon: catalog ++ entityRef: ${{ steps['register'].output.entityRef }} +diff --git a/lerna.json b/lerna.json +index 322929d..529a62f 100644 +--- a/lerna.json ++++ b/lerna.json +@@ -3,4 +3,4 @@ + "npmClient": "yarn", +- "useWorkspaces": true, +- "version": "0.1.0" ++ "version": "0.1.0", ++ "$schema": "node_modules/lerna/schemas/lerna-schema.json" + } +diff --git a/package.json b/package.json +index 2454755..2364da8 100644 +--- a/package.json ++++ b/package.json +@@ -5,3 +5,3 @@ + "engines": { +- "node": "14 || 16" ++ "node": "18 || 20" + }, +@@ -11,3 +11,4 @@ + "start-backend": "yarn workspace backend start", +- "build": "lerna run build", ++ "build:backend": "yarn workspace backend build", ++ "build:all": "backstage-cli repo build --all", + "build-image": "yarn workspace backend build-image", +@@ -15,11 +16,11 @@ + "tsc:full": "tsc --skipLibCheck false --incremental false", +- "clean": "backstage-cli clean && lerna run clean", +- "diff": "lerna run diff --", +- "test": "backstage-cli test", +- "test:all": "lerna run test -- --coverage", +- "lint": "lerna run lint --since origin/master --", +- "lint:all": "lerna run lint --", ++ "clean": "backstage-cli repo clean", ++ "test": "backstage-cli repo test", ++ "test:all": "backstage-cli repo test --coverage", ++ "test:e2e": "playwright test", ++ "fix": "backstage-cli repo fix", ++ "lint": "backstage-cli repo lint --since origin/master", ++ "lint:all": "backstage-cli repo lint", + "prettier:check": "prettier --check .", +- "create-plugin": "backstage-cli create-plugin --scope internal", +- "remove-plugin": "backstage-cli remove-plugin" ++ "new": "backstage-cli new --scope internal" + }, +@@ -32,8 +33,15 @@ + "devDependencies": { +- "@backstage/cli": "^0.12.0", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/e2e-test-utils": "^0.1.0", ++ "@playwright/test": "^1.32.3", + "@spotify/prettier-config": "^12.0.0", +- "concurrently": "^6.0.0", +- "lerna": "^4.0.0", ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^9.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.5.4" ++ "typescript": "~5.2.0" ++ }, ++ "resolutions": { ++ "@types/react": "^17", ++ "@types/react-dom": "^17" + }, +diff --git a/packages/README.md b/packages/README.md +new file mode 100644 +index 0000000..6327fa0 +--- /dev/null ++++ b/packages/README.md +@@ -0,0 +1,9 @@ ++# The Packages Folder ++ ++This is where your own applications and centrally managed libraries live, each ++in a separate folder of its own. ++ ++From the start there's an `app` folder (for the frontend) and a `backend` folder ++(for the Node backend), but you can also add more modules in here that house ++your core additions and adaptations, such as themes, common React component ++libraries, utilities, and similar. +diff --git a/packages/app/.eslintignore b/packages/app/.eslintignore +new file mode 100644 +index 0000000..a48cf0d +--- /dev/null ++++ b/packages/app/.eslintignore +@@ -0,0 +1 @@ ++public +diff --git a/packages/app/.eslintrc.js b/packages/app/.eslintrc.js +index 13573ef..e2a53a6 100644 +--- a/packages/app/.eslintrc.js ++++ b/packages/app/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/app/cypress.json b/packages/app/cypress.json +deleted file mode 100644 +index 5de7ebf..0000000 +--- a/packages/app/cypress.json ++++ /dev/null +@@ -1,5 +0,0 @@ +-{ +- "baseUrl": "http://localhost:3001", +- "fixturesFolder": false, +- "pluginsFile": false +-} +diff --git a/packages/app/cypress/.eslintrc.json b/packages/app/cypress/.eslintrc.json +deleted file mode 100644 +index 2b3a458..0000000 +--- a/packages/app/cypress/.eslintrc.json ++++ /dev/null +@@ -1,21 +0,0 @@ +-{ +- "plugins": ["cypress"], +- "extends": ["plugin:cypress/recommended"], +- "rules": { +- "jest/expect-expect": [ +- "error", +- { +- "assertFunctionNames": ["expect", "cy.contains"] +- } +- ], +- "import/no-extraneous-dependencies": [ +- "error", +- { +- "devDependencies": true, +- "optionalDependencies": true, +- "peerDependencies": true, +- "bundledDependencies": true +- } +- ] +- } +-} +diff --git a/packages/app/cypress/integration/app.js b/packages/app/cypress/integration/app.js +deleted file mode 100644 +index 43fb2e3..0000000 +--- a/packages/app/cypress/integration/app.js ++++ /dev/null +@@ -1,6 +0,0 @@ +-describe('App', () => { +- it('should render the catalog', () => { +- cy.visit('/'); +- cy.contains('My Company Catalog'); +- }); +-}); +diff --git a/packages/app/e2e-tests/app.test.ts b/packages/app/e2e-tests/app.test.ts +new file mode 100644 +index 0000000..d45bc0d +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,23 @@ ++/* ++ * Copyright 2020 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { test, expect } from '@playwright/test'; ++ ++test('App should render the welcome page', async ({ page }) => { ++ await page.goto('/'); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 8ce600d..2100405 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,22 +5,38 @@ + "bundled": true, ++ "backstage": { ++ "role": "frontend" ++ }, ++ "scripts": { ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "clean": "backstage-cli package clean", ++ "test": "backstage-cli package test", ++ "lint": "backstage-cli package lint" ++ }, + "dependencies": { +- "@backstage/app-defaults": "^0.1.5", +- "@backstage/catalog-model": "^0.9.10", +- "@backstage/cli": "^0.12.0", +- "@backstage/core-app-api": "^0.5.0", +- "@backstage/core-components": "^0.8.5", +- "@backstage/core-plugin-api": "^0.6.0", +- "@backstage/integration-react": "^0.1.19", +- "@backstage/plugin-api-docs": "^0.7.0", +- "@backstage/plugin-catalog": "^0.7.9", +- "@backstage/plugin-catalog-import": "^0.7.10", +- "@backstage/plugin-catalog-react": "^0.6.12", +- "@backstage/plugin-github-actions": "^0.4.32", +- "@backstage/plugin-org": "^0.4.0", +- "@backstage/plugin-scaffolder": "^0.12.0", +- "@backstage/plugin-search": "^0.5.6", +- "@backstage/plugin-tech-radar": "^0.5.3", +- "@backstage/plugin-techdocs": "^0.13.0", +- "@backstage/plugin-user-settings": "^0.3.17", +- "@backstage/theme": "^0.2.14", ++ "@backstage/app-defaults": "^1.4.5-next.1", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/core-app-api": "^1.11.1-next.0", ++ "@backstage/core-components": "^0.13.8-next.1", ++ "@backstage/core-plugin-api": "^1.8.0-next.0", ++ "@backstage/integration-react": "^1.1.21-next.1", ++ "@backstage/plugin-api-docs": "^0.9.13-next.1", ++ "@backstage/plugin-catalog": "^1.15.0-next.1", ++ "@backstage/plugin-catalog-common": "^1.0.17", ++ "@backstage/plugin-catalog-graph": "^0.2.38-next.1", ++ "@backstage/plugin-catalog-import": "^0.10.2-next.1", ++ "@backstage/plugin-catalog-react": "^1.9.0-next.1", ++ "@backstage/plugin-github-actions": "^0.6.7-next.1", ++ "@backstage/plugin-org": "^0.6.16-next.1", ++ "@backstage/plugin-permission-react": "^0.4.17-next.0", ++ "@backstage/plugin-scaffolder": "^1.16.0-next.1", ++ "@backstage/plugin-search": "^1.4.2-next.1", ++ "@backstage/plugin-search-react": "^1.7.2-next.1", ++ "@backstage/plugin-tech-radar": "^0.6.10-next.1", ++ "@backstage/plugin-techdocs": "^1.8.1-next.1", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.2-next.1", ++ "@backstage/plugin-techdocs-react": "^1.1.13-next.1", ++ "@backstage/plugin-user-settings": "^0.7.12-next.1", ++ "@backstage/theme": "^0.4.4-next.0", + "@material-ui/core": "^4.12.2", +@@ -30,29 +46,15 @@ + "react-dom": "^17.0.2", +- "react-router": "6.0.0-beta.0", +- "react-router-dom": "6.0.0-beta.0", +- "react-use": "^15.3.3" ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^0.2.3", ++ "@backstage/test-utils": "^1.4.5-next.0", ++ "@playwright/test": "^1.32.3", + "@testing-library/jest-dom": "^5.10.1", +- "@testing-library/react": "^10.4.1", +- "@testing-library/user-event": "^12.0.7", +- "@types/jest": "^26.0.7", +- "@types/node": "^14.14.32", ++ "@testing-library/react": "^12.1.3", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^8.0.0", + "@types/react-dom": "*", +- "cross-env": "^7.0.0", +- "cypress": "^7.3.0", +- "eslint-plugin-cypress": "^2.10.3", +- "start-server-and-test": "^1.10.11" +- }, +- "scripts": { +- "start": "backstage-cli app:serve", +- "build": "backstage-cli app:build", +- "clean": "backstage-cli clean", +- "test": "backstage-cli test", +- "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", +- "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", +- "lint": "backstage-cli lint", +- "cy:dev": "cypress open", +- "cy:run": "cypress run" ++ "cross-env": "^7.0.0" + }, +diff --git a/packages/app/public/index.html b/packages/app/public/index.html +index a936c73..fc758ee 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -10,3 +10,2 @@ + /> +- + +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 78949b0..8d62f29 100644 +--- a/packages/app/src/App.tsx ++++ b/packages/app/src/App.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { Navigate, Route } from 'react-router'; ++import { Navigate, Route } from 'react-router-dom'; + import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; +@@ -17,3 +17,2 @@ import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -22,2 +21,4 @@ import { + } from '@backstage/plugin-techdocs'; ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; + import { UserSettingsPage } from '@backstage/plugin-user-settings'; +@@ -30,3 +31,6 @@ import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; + import { createApp } from '@backstage/app-defaults'; +-import { FlatRoutes } from '@backstage/core-app-api'; ++import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; ++import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; ++import { RequirePermission } from '@backstage/plugin-permission-react'; ++import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; + +@@ -38,2 +42,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -44,2 +49,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -51,8 +57,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -64,5 +67,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -77,3 +82,10 @@ const routes = ( + /> +- } /> ++ ++ ++ ++ } ++ /> + }> +@@ -82,2 +94,3 @@ const routes = ( + } /> ++ } /> + +@@ -85,4 +98,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -92,5 +105,3 @@ const App = () => ( + +- ++ , + ); +- +-export default App; +diff --git a/packages/app/src/components/Root/LogoFull.tsx b/packages/app/src/components/Root/LogoFull.tsx +index c7b1c84..47e3b73 100644 +--- a/packages/app/src/components/Root/LogoFull.tsx ++++ b/packages/app/src/components/Root/LogoFull.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/LogoIcon.tsx b/packages/app/src/components/Root/LogoIcon.tsx +index 073cf6e..7eae8c7 100644 +--- a/packages/app/src/components/Root/LogoIcon.tsx ++++ b/packages/app/src/components/Root/LogoIcon.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx +index 7e98c7d..6768b48 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,19 +1,3 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- +-import React, { useContext, PropsWithChildren } from 'react'; +-import { Link, makeStyles } from '@material-ui/core'; ++import React, { PropsWithChildren } from 'react'; ++import { makeStyles } from '@material-ui/core'; + import HomeIcon from '@material-ui/icons/Home'; +@@ -25,3 +9,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -30,6 +13,3 @@ import { + } from '@backstage/plugin-user-settings'; +-import { +- SidebarSearchModal, +- SearchContextProvider, +-} from '@backstage/plugin-search'; ++import { SidebarSearchModal } from '@backstage/plugin-search'; + import { +@@ -37,3 +17,2 @@ import { + sidebarConfig, +- SidebarContext, + SidebarDivider, +@@ -44,2 +23,4 @@ import { + SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -65,3 +46,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -69,8 +50,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -86,5 +62,3 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + } to="/search"> +- +- +- {' '} ++ + +diff --git a/packages/app/src/components/Root/index.ts b/packages/app/src/components/Root/index.ts +index dff706f..3528881 100644 +--- a/packages/app/src/components/Root/index.ts ++++ b/packages/app/src/components/Root/index.ts +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + export { Root } from './Root'; +diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx +index d3b4b78..6722ea2 100644 +--- a/packages/app/src/components/catalog/EntityPage.tsx ++++ b/packages/app/src/components/catalog/EntityPage.tsx +@@ -1,16 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ + import React from 'react'; +@@ -29,3 +14,2 @@ import { + EntityDependsOnResourcesCard, +- EntitySystemDiagramCard, + EntityHasComponentsCard, +@@ -43,2 +27,4 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +@@ -56,2 +42,27 @@ import { EntityTechdocsContent } from '@backstage/plugin-techdocs'; + import { EmptyState } from '@backstage/core-components'; ++import { ++ Direction, ++ EntityCatalogGraphCard, ++} from '@backstage/plugin-catalog-graph'; ++import { ++ RELATION_API_CONSUMED_BY, ++ RELATION_API_PROVIDED_BY, ++ RELATION_CONSUMES_API, ++ RELATION_DEPENDENCY_OF, ++ RELATION_DEPENDS_ON, ++ RELATION_HAS_PART, ++ RELATION_PART_OF, ++ RELATION_PROVIDES_API, ++} from '@backstage/catalog-model'; ++ ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); + +@@ -94,2 +105,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -110,2 +129,6 @@ const overviewContent = ( + ++ ++ ++ ++ + +@@ -152,3 +175,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -179,3 +202,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -198,3 +221,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -225,2 +248,5 @@ const apiPage = ( + ++ ++ ++ + +@@ -292,3 +318,9 @@ const systemPage = ( + +- ++ ++ ++ ++ ++ ++ ++ + +@@ -304,3 +336,19 @@ const systemPage = ( + +- ++ + +@@ -317,2 +365,5 @@ const domainPage = ( + ++ ++ ++ + +diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx +index cf380b6..1788dde 100644 +--- a/packages/app/src/components/search/SearchPage.tsx ++++ b/packages/app/src/components/search/SearchPage.tsx +@@ -1,7 +1,12 @@ + import React from 'react'; +-import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; ++import { makeStyles, Theme, Grid, Paper } from '@material-ui/core'; + +-import { CatalogResultListItem } from '@backstage/plugin-catalog'; +-import { DocsResultListItem } from '@backstage/plugin-techdocs'; ++import { CatalogSearchResultListItem } from '@backstage/plugin-catalog'; ++import { ++ catalogApiRef, ++ CATALOG_FILTER_EXISTS, ++} from '@backstage/plugin-catalog-react'; ++import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; + ++import { SearchType } from '@backstage/plugin-search'; + import { +@@ -10,5 +15,5 @@ import { + SearchResult, +- SearchType, +- DefaultResultListItem, +-} from '@backstage/plugin-search'; ++ SearchPagination, ++ useSearch, ++} from '@backstage/plugin-search-react'; + import { +@@ -20,2 +25,3 @@ import { + } from '@backstage/core-components'; ++import { useApi } from '@backstage/core-plugin-api'; + +@@ -38,2 +44,4 @@ const SearchPage = () => { + const classes = useStyles(); ++ const { types } = useSearch(); ++ const catalogApi = useApi(catalogApiRef); + +@@ -67,4 +75,26 @@ const SearchPage = () => { + ++ {types.includes('techdocs') && ( ++ { ++ // Return a list of entities which are documented. ++ const { items } = await catalogApi.getEntities({ ++ fields: ['metadata.name'], ++ filter: { ++ 'metadata.annotations.backstage.io/techdocs-ref': ++ CATALOG_FILTER_EXISTS, ++ }, ++ }); ++ ++ const names = items.map(entity => entity.metadata.name); ++ names.sort(); ++ return names; ++ }} ++ /> ++ )} + { + className={classes.filter} ++ label="Lifecycle" + name="lifecycle" +@@ -80,32 +111,6 @@ const SearchPage = () => { + ++ + +- {({ results }) => ( +- +- {results.map(({ type, document }) => { +- switch (type) { +- case 'software-catalog': +- return ( +- +- ); +- case 'techdocs': +- return ( +- +- ); +- default: +- return ( +- +- ); +- } +- })} +- +- )} ++ } /> ++ } /> + +diff --git a/packages/backend/.eslintrc.js b/packages/backend/.eslintrc.js +index 16a033d..e2a53a6 100644 +--- a/packages/backend/.eslintrc.js ++++ b/packages/backend/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint.backend')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index 31231a3..18548e9 100644 +--- a/packages/backend/Dockerfile ++++ b/packages/backend/Dockerfile +@@ -7,3 +7,3 @@ + # yarn tsc +-# yarn build ++# yarn build:backend + # +@@ -11,6 +11,29 @@ + +-FROM node:14-buster-slim ++FROM node:18-bookworm-slim + ++# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends python3 g++ build-essential && \ ++ yarn config set python /usr/bin/python3 ++ ++# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, ++# in which case you should also move better-sqlite3 to "devDependencies" in package.json. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends libsqlite3-dev ++ ++# From here on we use the least-privileged `node` user to run the backend. ++USER node ++ ++# This should create the app dir as `node`. ++# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. ++# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. + WORKDIR /app + ++# This switches many Node.js dependencies to production mode. ++ENV NODE_ENV production ++ + # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. +@@ -18,11 +41,12 @@ WORKDIR /app + # and along with yarn.lock and the root package.json, that's enough to run yarn install. +-COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ ++COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ + RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz + +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + + # Then copy the rest of the backend bundle, along with any other files we might want. +-COPY packages/backend/dist/bundle.tar.gz app-config.yaml ./ ++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"] +diff --git a/packages/backend/README.md b/packages/backend/README.md +index 02426ef..3607b0a 100644 +--- a/packages/backend/README.md ++++ b/packages/backend/README.md +@@ -18,4 +18,2 @@ To run the example backend, first go to the project root and run + yarn install +-yarn tsc +-yarn build + ``` +@@ -27,7 +25,2 @@ After that, go to the `packages/backend` directory and run + ```bash +-AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x \ +-AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x \ +-AUTH_OAUTH2_CLIENT_ID=x AUTH_OAUTH2_CLIENT_SECRET=x \ +-AUTH_OAUTH2_AUTH_URL=x AUTH_OAUTH2_TOKEN_URL=x \ +-LOG_LEVEL=debug \ + yarn start +@@ -35,4 +28,4 @@ yarn start + +-Substitute `x` for actual values, or leave them as dummy values just to try out +-the backend without using the auth or sentry features. ++If you want to override any configuration locally, for example adding any secrets, ++you can do so in `app-config.local.yaml`. + +@@ -45,3 +38,3 @@ locations to the backend. These are places where the backend can find some + entity descriptor data to consume and serve. For more information, see +-[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog). ++[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). + +@@ -65,2 +58,2 @@ and + - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +-- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) ++- [Backstage Documentation](https://backstage.io/docs) +diff --git a/packages/backend/package.json b/packages/backend/package.json +index 5d64224..f224eaf 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,30 +6,33 @@ + "private": true, ++ "backstage": { ++ "role": "backend" ++ }, + "scripts": { +- "build": "backstage-cli backend:bundle", +- "build-image": "docker build ../.. -f Dockerfile --tag backstage", +- "start": "backstage-cli backend:dev", +- "lint": "backstage-cli lint", +- "test": "backstage-cli test", +- "clean": "backstage-cli clean", +- "migrate:create": "knex migrate:make -x ts" ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "lint": "backstage-cli package lint", ++ "test": "backstage-cli package test", ++ "clean": "backstage-cli package clean", ++ "build-image": "docker build ../.. -f Dockerfile --tag backstage" + }, + "dependencies": { +- "app": "0.0.0", +- "@backstage/backend-common": "^0.10.4", +- "@backstage/backend-tasks": "^0.1.4", +- "@backstage/catalog-model": "^0.9.10", +- "@backstage/catalog-client": "^0.5.5", +- "@backstage/config": "^0.1.13", +- "@backstage/plugin-app-backend": "^0.3.22", +- "@backstage/plugin-auth-backend": "^0.7.0", +- "@backstage/plugin-catalog-backend": "^0.21.0", +- "@backstage/plugin-permission-common": "^0.4.0", +- "@backstage/plugin-permission-node": "^0.4.0", +- "@backstage/plugin-proxy-backend": "^0.2.16", +- "@backstage/plugin-scaffolder-backend": "^0.15.21", +- "@backstage/plugin-search-backend": "^0.3.1", +- "@backstage/plugin-search-backend-node": "^0.4.4", +- "@backstage/plugin-techdocs-backend": "^0.13.0", +- "@gitbeaker/node": "^34.6.0", +- "@octokit/rest": "^18.5.3", ++ "@backstage/backend-common": "^0.19.9-next.1", ++ "@backstage/backend-tasks": "^0.5.12-next.1", ++ "@backstage/catalog-client": "^1.4.5", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/config": "^1.1.1", ++ "@backstage/plugin-app-backend": "^0.3.55-next.1", ++ "@backstage/plugin-auth-backend": "^0.20.0-next.1", ++ "@backstage/plugin-auth-node": "^0.4.1-next.1", ++ "@backstage/plugin-catalog-backend": "^1.15.0-next.1", ++ "@backstage/plugin-permission-common": "^0.7.9", ++ "@backstage/plugin-permission-node": "^0.7.18-next.1", ++ "@backstage/plugin-proxy-backend": "^0.4.5-next.1", ++ "@backstage/plugin-scaffolder-backend": "^1.19.0-next.1", ++ "@backstage/plugin-search-backend": "^1.4.7-next.1", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.16-next.1", ++ "@backstage/plugin-search-backend-node": "^1.2.11-next.1", ++ "@backstage/plugin-techdocs-backend": "^1.9.0-next.1", ++ "app": "link:../app", ++ "better-sqlite3": "^8.0.0", + "dockerode": "^3.3.1", +@@ -37,4 +40,3 @@ + "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "pg": "^8.3.0", + "winston": "^3.2.1" +@@ -42,6 +44,7 @@ + "devDependencies": { +- "@backstage/cli": "^0.12.0", ++ "@backstage/cli": "^0.24.0-next.1", + "@types/dockerode": "^3.3.0", + "@types/express": "^4.17.6", +- "@types/express-serve-static-core": "^4.17.5" ++ "@types/express-serve-static-core": "^4.17.5", ++ "@types/luxon": "^2.0.4" + }, +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 70bc66b..04c4ff9 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -17,3 +17,3 @@ import { + DatabaseManager, +- SingleHostDiscovery, ++ HostDiscovery, + UrlReaders, +@@ -32,2 +32,3 @@ import { PluginEnvironment } from './types'; + import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; + +@@ -36,7 +37,11 @@ function makeCreateEnv(config: Config) { + const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++ const discovery = HostDiscovery.fromConfig(config); + const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++ const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); + const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); ++ const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); ++ ++ const identity = DefaultIdentityClient.create({ ++ discovery, ++ }); + const permissions = ServerPermissionClient.fromConfig(config, { +@@ -63,2 +68,3 @@ function makeCreateEnv(config: Config) { + permissions, ++ identity, + }; +@@ -106,3 +112,3 @@ module.hot?.accept(); + main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); ++ console.error('Backend failed to start up', error); + process.exit(1); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +index 14e19a1..7c37f68 100644 +--- a/packages/backend/src/plugins/app.ts ++++ b/packages/backend/src/plugins/app.ts +@@ -4,11 +4,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- database, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + return await createRouter({ +- logger, +- config, +- database, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, + appPackageName: 'app', +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +index 5216510..77eb6aa 100644 +--- a/packages/backend/src/plugins/auth.ts ++++ b/packages/backend/src/plugins/auth.ts +@@ -1,2 +1,6 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; ++import { ++ createRouter, ++ providers, ++ defaultAuthProviderFactories, ++} from '@backstage/plugin-auth-backend'; + import { Router } from 'express'; +@@ -4,9 +8,47 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, database, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, ++ providerFactories: { ++ ...defaultAuthProviderFactories, ++ ++ // This replaces the default GitHub auth provider with a customized one. ++ // The `signIn` option enables sign-in for this provider, using the ++ // identity resolution logic that's provided in the `resolver` callback. ++ // ++ // This particular resolver makes all users share a single "guest" identity. ++ // It should only be used for testing and trying out Backstage. ++ // ++ // If you want to use a production ready resolver you can switch to ++ // the one that is commented out below, it looks up a user entity in the ++ // catalog using the GitHub username of the authenticated user. ++ // That resolver requires you to have user entities populated in the catalog, ++ // for example using https://backstage.io/docs/integrations/github/org ++ // ++ // There are other resolvers to choose from, and you can also create ++ // your own, see the auth documentation for more details: ++ // ++ // https://backstage.io/docs/auth/identity-resolver ++ github: providers.github.create({ ++ signIn: { ++ resolver(_, ctx) { ++ const userRef = 'user:default/guest'; // Must be a full entity reference ++ return ctx.issueToken({ ++ claims: { ++ sub: userRef, // The user's own identity ++ ent: [userRef], // A list of identities that the user claims ownership through ++ }, ++ }); ++ }, ++ // resolver: providers.github.resolvers.usernameMatchingUserEntityName(), ++ }, ++ }), ++ }, ++ }); + } +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +index 506f6d9..54ec393 100644 +--- a/packages/backend/src/plugins/proxy.ts ++++ b/packages/backend/src/plugins/proxy.ts +@@ -4,8 +4,10 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ }); + } +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +index 6be2e97..a12fee2 100644 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ b/packages/backend/src/plugins/scaffolder.ts +@@ -1,5 +1,3 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; + import { CatalogClient } from '@backstage/catalog-client'; + import { createRouter } from '@backstage/plugin-scaffolder-backend'; +-import Docker from 'dockerode'; + import { Router } from 'express'; +@@ -7,20 +5,17 @@ import type { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- database, +- reader, +- discovery, +-}: PluginEnvironment): Promise { +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- const catalogClient = new CatalogClient({ discoveryApi: discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ const catalogClient = new CatalogClient({ ++ discoveryApi: env.discovery, ++ }); + + return await createRouter({ +- containerRunner, +- logger, +- config, +- database, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ reader: env.reader, + catalogClient, +- reader, ++ identity: env.identity, ++ permissions: env.permissions, + }); +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +index f23b0c7..e9469dc 100644 +--- a/packages/backend/src/plugins/search.ts ++++ b/packages/backend/src/plugins/search.ts +@@ -7,14 +7,25 @@ import { + import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend'; ++import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend'; ++import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; ++import { Router } from 'express'; + +-export default async function createPlugin({ +- logger, +- discovery, +- config, +- tokenManager, +-}: PluginEnvironment) { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); ++ const searchEngine = new LunrSearchEngine({ ++ logger: env.logger, ++ }); ++ const indexBuilder = new IndexBuilder({ ++ logger: env.logger, ++ searchEngine, ++ }); ++ ++ const schedule = env.scheduler.createScheduledTaskRunner({ ++ frequency: { minutes: 10 }, ++ timeout: { minutes: 15 }, ++ // A 3 second delay gives the backend server a chance to initialize before ++ // any collators are executed, which may attempt requests against the API. ++ initialDelay: { seconds: 3 }, ++ }); + +@@ -23,6 +34,6 @@ export default async function createPlugin({ + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { +- discovery, +- tokenManager, ++ schedule, ++ factory: DefaultCatalogCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, + }), +@@ -32,7 +43,7 @@ export default async function createPlugin({ + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultTechDocsCollator.fromConfig(config, { +- discovery, +- logger, +- tokenManager, ++ schedule, ++ factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ logger: env.logger, ++ tokenManager: env.tokenManager, + }), +@@ -43,6 +54,4 @@ export default async function createPlugin({ + const { scheduler } = await indexBuilder.build(); ++ scheduler.start(); + +- // A 3 second delay gives the backend server a chance to initialize before +- // any collators are executed, which may attempt requests against the API. +- setTimeout(() => scheduler.start(), 3000); + useHotCleanup(module, () => scheduler.stop()); +@@ -51,3 +60,6 @@ export default async function createPlugin({ + engine: indexBuilder.getSearchEngine(), +- logger, ++ types: indexBuilder.getDocumentTypes(), ++ permissions: env.permissions, ++ config: env.config, ++ logger: env.logger, + }); +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +index 054c64d..be8bb0c 100644 +--- a/packages/backend/src/plugins/techdocs.ts ++++ b/packages/backend/src/plugins/techdocs.ts +@@ -11,13 +11,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +- cache, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, ++ const preparers = await Preparers.fromConfig(env.config, { ++ logger: env.logger, ++ reader: env.reader, + }); +@@ -29,4 +25,4 @@ export default async function createPlugin({ + // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, ++ const generators = await Generators.fromConfig(env.config, { ++ logger: env.logger, + containerRunner, +@@ -37,5 +33,5 @@ export default async function createPlugin({ + // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, ++ const publisher = await Publisher.fromConfig(env.config, { ++ logger: env.logger, ++ discovery: env.discovery, + }); +@@ -49,6 +45,6 @@ export default async function createPlugin({ + publisher, +- logger, +- config, +- discovery, +- cache, ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ cache: env.cache, + }); +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +index 0862b0e..9cd2c74 100644 +--- a/packages/backend/src/types.ts ++++ b/packages/backend/src/types.ts +@@ -10,3 +10,4 @@ import { + import { PluginTaskScheduler } from '@backstage/backend-tasks'; +-import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; ++import { PermissionEvaluator } from '@backstage/plugin-permission-common'; ++import { IdentityApi } from '@backstage/plugin-auth-node'; + +@@ -21,3 +22,4 @@ export type PluginEnvironment = { + scheduler: PluginTaskScheduler; +- permissions: PermissionAuthorizer; ++ permissions: PermissionEvaluator; ++ identity: IdentityApi; + }; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..37c7fb1 +--- /dev/null ++++ b/playwright.config.ts +@@ -0,0 +1,60 @@ ++/* ++ * Copyright 2023 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { defineConfig } from '@playwright/test'; ++import { generateProjects } from '@backstage/e2e-test-utils/playwright'; ++ ++/** ++ * See https://playwright.dev/docs/test-configuration. ++ */ ++export default defineConfig({ ++ timeout: 60_000, ++ ++ expect: { ++ timeout: 5_000, ++ }, ++ ++ // Run your local dev server before starting the tests ++ webServer: process.env.CI ++ ? [] ++ : [ ++ { ++ command: 'yarn start', ++ port: 3000, ++ reuseExistingServer: true, ++ timeout: 60_000, ++ }, ++ ], ++ ++ forbidOnly: !!process.env.CI, ++ ++ retries: process.env.CI ? 2 : 0, ++ ++ reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]], ++ ++ use: { ++ actionTimeout: 0, ++ baseURL: ++ process.env.PLAYWRIGHT_URL ?? ++ (process.env.CI ? 'http://localhost:7007' : 'http://localhost:3000'), ++ screenshot: 'only-on-failure', ++ trace: 'on-first-retry', ++ }, ++ ++ outputDir: 'node_modules/.cache/e2e-test-results', ++ ++ projects: generateProjects(), // Find all packages with e2e-test folders ++}); +diff --git a/plugins/README.md b/plugins/README.md +new file mode 100644 +index 0000000..d7865fd +--- /dev/null ++++ b/plugins/README.md +@@ -0,0 +1,9 @@ ++# The Plugins Folder ++ ++This is where your own plugins and their associated modules live, each in a ++separate folder of its own. ++ ++If you want to create a new plugin here, go to your project root directory, run ++the command `yarn new`, and follow the on-screen instructions. ++ ++You can also check out existing plugins on [the plugin marketplace](https://backstage.io/plugins)! +diff --git a/yarn.lock b/yarn.lock +new file mode 100644 +index 0000000..5ad7fe2 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1 @@ ++# intentionally left empty diff --git a/diffs/0.4.15..0.5.7-next.1.diff b/diffs/0.4.15..0.5.7-next.1.diff new file mode 100644 index 00000000..cded9a02 --- /dev/null +++ b/diffs/0.4.15..0.5.7-next.1.diff @@ -0,0 +1,1675 @@ +diff --git a/.dockerignore b/.dockerignore +index 63c9c34..05edb62 100644 +--- a/.dockerignore ++++ b/.dockerignore +@@ -1,5 +1,8 @@ + .git ++.yarn/cache ++.yarn/install-state.gz + node_modules +-packages +-!packages/backend/dist ++packages/*/src ++packages/*/node_modules + plugins ++*.local.yaml +diff --git a/.eslintignore b/.eslintignore +new file mode 100644 +index 0000000..e5b1994 +--- /dev/null ++++ b/.eslintignore +@@ -0,0 +1 @@ ++playwright.config.ts +diff --git a/.gitignore b/.gitignore +index d16a8d3..fbf8139 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -17,2 +17,11 @@ node_modules/ + ++# Yarn 3 files ++.pnp.* ++.yarn/* ++!.yarn/patches ++!.yarn/plugins ++!.yarn/releases ++!.yarn/sdks ++!.yarn/versions ++ + # Node version directives +@@ -39 +48,7 @@ site + *-credentials.yaml ++ ++# vscode database functionality support files ++*.session.sql ++ ++# E2E test reports ++e2e-test-report/ +diff --git a/app-config.production.yaml b/app-config.production.yaml +index 5e36c23..8f0751c 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -1,3 +1,3 @@ + app: +- # Should be the same as backend.baseUrl when using the `app-backend` plugin ++ # Should be the same as backend.baseUrl when using the `app-backend` plugin. + baseUrl: http://localhost:7007 +@@ -5,4 +5,31 @@ app: + backend: ++ # Note that the baseUrl should be the URL that the browser and other clients ++ # should use when communicating with the backend, i.e. it needs to be ++ # reachable not just from within the backend host, but from all of your ++ # callers. When its value is "http://localhost:7007", it's strictly private ++ # and can't be reached by others. + baseUrl: http://localhost:7007 +- listen: +- port: 7007 ++ # The listener can also be expressed as a single : string. In this case we bind to ++ # all interfaces, the most permissive setting. The right value depends on your specific deployment. ++ listen: ':7007' ++ ++ # config options: https://node-postgres.com/api/client ++ database: ++ client: pg ++ connection: ++ host: ${POSTGRES_HOST} ++ port: ${POSTGRES_PORT} ++ user: ${POSTGRES_USER} ++ password: ${POSTGRES_PASSWORD} ++ # https://node-postgres.com/features/ssl ++ # you can set the sslmode configuration option via the `PGSSLMODE` environment variable ++ # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require) ++ # ssl: ++ # ca: # if you have a CA file and want to verify it you can uncomment this section ++ # $file: /ca/server.crt ++ ++catalog: ++ # Overrides the default list locations from app-config.yaml as these contain example data. ++ # See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details ++ # on how to get entities into the catalog. ++ locations: [] +diff --git a/app-config.yaml b/app-config.yaml +index 1a622a2..08d2abf 100644 +--- a/app-config.yaml ++++ b/app-config.yaml +@@ -9,3 +9,4 @@ backend: + # Used for enabling authentication, secret is shared by all backend plugins +- # See backend-to-backend-auth.md in the docs for information on the format ++ # See https://backstage.io/docs/auth/service-to-service-auth for ++ # information on the format + # auth: +@@ -16,2 +17,4 @@ backend: + port: 7007 ++ # Uncomment the following host directive to bind to specific interfaces ++ # host: 127.0.0.1 + csp: +@@ -22,9 +25,9 @@ backend: + origin: http://localhost:3000 +- methods: [GET, POST, PUT, DELETE] ++ methods: [GET, HEAD, PATCH, POST, PUT, DELETE] + credentials: true ++ # This is for local development only, it is not recommended to use this in production ++ # The production database configuration is stored in app-config.production.yaml + database: +- client: sqlite3 ++ client: better-sqlite3 + connection: ':memory:' +- cache: +- store: memory + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir +@@ -34,2 +37,4 @@ integrations: + - host: github.com ++ # This is a Personal Access Token or PAT from GitHub. You can find out how to generate this token, and more information ++ # about setting up the GitHub integration here: https://backstage.io/docs/getting-started/configuration#setting-up-a-github-integration + token: ${GITHUB_TOKEN} +@@ -41,5 +46,8 @@ integrations: + proxy: +- '/test': +- target: 'https://example.com' +- changeOrigin: true ++ ### Example for how to add a proxy endpoint for the frontend. ++ ### A typical reason to do this is to handle HTTPS and CORS for internal services. ++ # endpoints: ++ # '/test': ++ # target: 'https://example.com' ++ # changeOrigin: true + +@@ -64,41 +72,32 @@ scaffolder: + catalog: ++ import: ++ entityFilename: catalog-info.yaml ++ pullRequestBranchName: backstage-integration + rules: +- - allow: [Component, System, API, Group, User, Resource, Location] ++ - allow: [Component, System, API, Resource, Location] + locations: +- # Backstage example components +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-components.yaml ++ # Local example data, file locations are relative to the backend process, typically `packages/backend` ++ - type: file ++ target: ../../examples/entities.yaml + +- # Backstage example systems +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-systems.yaml +- +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml +- +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml +- +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.yaml +- +- # Backstage example templates +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/react-ssr-template/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/springboot-grpc-template/template.yaml ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml + rules: + - allow: [Template] +- - type: url +- target: https://github.com/spotify/cookiecutter-golang/blob/master/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/docs-template/template.yaml ++ ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml + rules: +- - allow: [Template] ++ - allow: [User, Group] ++ ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml ++ ++ ## Uncomment these lines to add an example org ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme-corp.yaml ++ # rules: ++ # - allow: [User, Group] +diff --git a/backstage.json b/backstage.json +index e27bbc7..707af29 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "0.4.15" ++ "version": "1.20.0-next.1" + } +diff --git a/examples/entities.yaml b/examples/entities.yaml +new file mode 100644 +index 0000000..447e8b1 +--- /dev/null ++++ b/examples/entities.yaml +@@ -0,0 +1,41 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system ++apiVersion: backstage.io/v1alpha1 ++kind: System ++metadata: ++ name: examples ++spec: ++ owner: guests ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: example-website ++spec: ++ type: website ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ providesApis: [example-grpc-api] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api ++apiVersion: backstage.io/v1alpha1 ++kind: API ++metadata: ++ name: example-grpc-api ++spec: ++ type: grpc ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ definition: | ++ syntax = "proto3"; ++ ++ service Exampler { ++ rpc Example (ExampleMessage) returns (ExampleMessage) {}; ++ } ++ ++ message ExampleMessage { ++ string example = 1; ++ }; +diff --git a/examples/org.yaml b/examples/org.yaml +new file mode 100644 +index 0000000..a10e81f +--- /dev/null ++++ b/examples/org.yaml +@@ -0,0 +1,17 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user ++apiVersion: backstage.io/v1alpha1 ++kind: User ++metadata: ++ name: guest ++spec: ++ memberOf: [guests] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group ++apiVersion: backstage.io/v1alpha1 ++kind: Group ++metadata: ++ name: guests ++spec: ++ type: team ++ children: [] +diff --git a/examples/template/content/catalog-info.yaml b/examples/template/content/catalog-info.yaml +new file mode 100644 +index 0000000..d4ccca4 +--- /dev/null ++++ b/examples/template/content/catalog-info.yaml +@@ -0,0 +1,8 @@ ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: ${{ values.name | dump }} ++spec: ++ type: service ++ owner: user:guest ++ lifecycle: experimental +diff --git a/examples/template/content/index.js b/examples/template/content/index.js +new file mode 100644 +index 0000000..071ce5a +--- /dev/null ++++ b/examples/template/content/index.js +@@ -0,0 +1 @@ ++console.log('Hello from ${{ values.name }}!'); +diff --git a/examples/template/content/package.json b/examples/template/content/package.json +new file mode 100644 +index 0000000..86f968a +--- /dev/null ++++ b/examples/template/content/package.json +@@ -0,0 +1,5 @@ ++{ ++ "name": "${{ values.name }}", ++ "private": true, ++ "dependencies": {} ++} +diff --git a/examples/template/template.yaml b/examples/template/template.yaml +new file mode 100644 +index 0000000..33f262b +--- /dev/null ++++ b/examples/template/template.yaml +@@ -0,0 +1,74 @@ ++apiVersion: scaffolder.backstage.io/v1beta3 ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template ++kind: Template ++metadata: ++ name: example-nodejs-template ++ title: Example Node.js Template ++ description: An example template for the scaffolder that creates a simple Node.js service ++spec: ++ owner: user:guest ++ type: service ++ ++ # These parameters are used to generate the input form in the frontend, and are ++ # used to gather input data for the execution of the template. ++ parameters: ++ - title: Fill in some steps ++ required: ++ - name ++ properties: ++ name: ++ title: Name ++ type: string ++ description: Unique name of the component ++ ui:autofocus: true ++ ui:options: ++ rows: 5 ++ - title: Choose a location ++ required: ++ - repoUrl ++ properties: ++ repoUrl: ++ title: Repository Location ++ type: string ++ ui:field: RepoUrlPicker ++ ui:options: ++ allowedHosts: ++ - github.com ++ ++ # These steps are executed in the scaffolder backend, using data that we gathered ++ # via the parameters above. ++ steps: ++ # Each step executes an action, in this case one templates files into the working directory. ++ - id: fetch-base ++ name: Fetch Base ++ action: fetch:template ++ input: ++ url: ./content ++ values: ++ name: ${{ parameters.name }} ++ ++ # This step publishes the contents of the working directory to GitHub. ++ - id: publish ++ name: Publish ++ action: publish:github ++ input: ++ allowedHosts: ['github.com'] ++ description: This is ${{ parameters.name }} ++ repoUrl: ${{ parameters.repoUrl }} ++ ++ # The final step is to register our new component in the catalog. ++ - id: register ++ name: Register ++ action: catalog:register ++ input: ++ repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} ++ catalogInfoPath: '/catalog-info.yaml' ++ ++ # Outputs are displayed to the user after a successful execution of the template. ++ output: ++ links: ++ - title: Repository ++ url: ${{ steps['publish'].output.remoteUrl }} ++ - title: Open in catalog ++ icon: catalog ++ entityRef: ${{ steps['register'].output.entityRef }} +diff --git a/lerna.json b/lerna.json +index 322929d..529a62f 100644 +--- a/lerna.json ++++ b/lerna.json +@@ -3,4 +3,4 @@ + "npmClient": "yarn", +- "useWorkspaces": true, +- "version": "0.1.0" ++ "version": "0.1.0", ++ "$schema": "node_modules/lerna/schemas/lerna-schema.json" + } +diff --git a/package.json b/package.json +index d6ac827..2364da8 100644 +--- a/package.json ++++ b/package.json +@@ -5,3 +5,3 @@ + "engines": { +- "node": "14 || 16" ++ "node": "18 || 20" + }, +@@ -11,3 +11,4 @@ + "start-backend": "yarn workspace backend start", +- "build": "lerna run build", ++ "build:backend": "yarn workspace backend build", ++ "build:all": "backstage-cli repo build --all", + "build-image": "yarn workspace backend build-image", +@@ -15,11 +16,11 @@ + "tsc:full": "tsc --skipLibCheck false --incremental false", +- "clean": "backstage-cli clean && lerna run clean", +- "diff": "lerna run diff --", +- "test": "backstage-cli test", +- "test:all": "lerna run test -- --coverage", +- "lint": "lerna run lint --since origin/master --", +- "lint:all": "lerna run lint --", ++ "clean": "backstage-cli repo clean", ++ "test": "backstage-cli repo test", ++ "test:all": "backstage-cli repo test --coverage", ++ "test:e2e": "playwright test", ++ "fix": "backstage-cli repo fix", ++ "lint": "backstage-cli repo lint --since origin/master", ++ "lint:all": "backstage-cli repo lint", + "prettier:check": "prettier --check .", +- "create-plugin": "backstage-cli create-plugin --scope internal", +- "remove-plugin": "backstage-cli remove-plugin" ++ "new": "backstage-cli new --scope internal" + }, +@@ -32,8 +33,15 @@ + "devDependencies": { +- "@backstage/cli": "^0.13.0", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/e2e-test-utils": "^0.1.0", ++ "@playwright/test": "^1.32.3", + "@spotify/prettier-config": "^12.0.0", +- "concurrently": "^6.0.0", +- "lerna": "^4.0.0", ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^9.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.5.4" ++ "typescript": "~5.2.0" ++ }, ++ "resolutions": { ++ "@types/react": "^17", ++ "@types/react-dom": "^17" + }, +diff --git a/packages/README.md b/packages/README.md +new file mode 100644 +index 0000000..6327fa0 +--- /dev/null ++++ b/packages/README.md +@@ -0,0 +1,9 @@ ++# The Packages Folder ++ ++This is where your own applications and centrally managed libraries live, each ++in a separate folder of its own. ++ ++From the start there's an `app` folder (for the frontend) and a `backend` folder ++(for the Node backend), but you can also add more modules in here that house ++your core additions and adaptations, such as themes, common React component ++libraries, utilities, and similar. +diff --git a/packages/app/.eslintignore b/packages/app/.eslintignore +new file mode 100644 +index 0000000..a48cf0d +--- /dev/null ++++ b/packages/app/.eslintignore +@@ -0,0 +1 @@ ++public +diff --git a/packages/app/.eslintrc.js b/packages/app/.eslintrc.js +index 13573ef..e2a53a6 100644 +--- a/packages/app/.eslintrc.js ++++ b/packages/app/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/app/cypress.json b/packages/app/cypress.json +deleted file mode 100644 +index 5de7ebf..0000000 +--- a/packages/app/cypress.json ++++ /dev/null +@@ -1,5 +0,0 @@ +-{ +- "baseUrl": "http://localhost:3001", +- "fixturesFolder": false, +- "pluginsFile": false +-} +diff --git a/packages/app/cypress/.eslintrc.json b/packages/app/cypress/.eslintrc.json +deleted file mode 100644 +index 2b3a458..0000000 +--- a/packages/app/cypress/.eslintrc.json ++++ /dev/null +@@ -1,21 +0,0 @@ +-{ +- "plugins": ["cypress"], +- "extends": ["plugin:cypress/recommended"], +- "rules": { +- "jest/expect-expect": [ +- "error", +- { +- "assertFunctionNames": ["expect", "cy.contains"] +- } +- ], +- "import/no-extraneous-dependencies": [ +- "error", +- { +- "devDependencies": true, +- "optionalDependencies": true, +- "peerDependencies": true, +- "bundledDependencies": true +- } +- ] +- } +-} +diff --git a/packages/app/cypress/integration/app.js b/packages/app/cypress/integration/app.js +deleted file mode 100644 +index 43fb2e3..0000000 +--- a/packages/app/cypress/integration/app.js ++++ /dev/null +@@ -1,6 +0,0 @@ +-describe('App', () => { +- it('should render the catalog', () => { +- cy.visit('/'); +- cy.contains('My Company Catalog'); +- }); +-}); +diff --git a/packages/app/e2e-tests/app.test.ts b/packages/app/e2e-tests/app.test.ts +new file mode 100644 +index 0000000..d45bc0d +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,23 @@ ++/* ++ * Copyright 2020 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { test, expect } from '@playwright/test'; ++ ++test('App should render the welcome page', async ({ page }) => { ++ await page.goto('/'); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 1681062..2100405 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,23 +5,38 @@ + "bundled": true, ++ "backstage": { ++ "role": "frontend" ++ }, ++ "scripts": { ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "clean": "backstage-cli package clean", ++ "test": "backstage-cli package test", ++ "lint": "backstage-cli package lint" ++ }, + "dependencies": { +- "@backstage/app-defaults": "^0.1.5", +- "@backstage/catalog-model": "^0.9.10", +- "@backstage/cli": "^0.13.0", +- "@backstage/core-app-api": "^0.5.1", +- "@backstage/core-components": "^0.8.6", +- "@backstage/core-plugin-api": "^0.6.0", +- "@backstage/integration-react": "^0.1.19", +- "@backstage/plugin-api-docs": "^0.7.0", +- "@backstage/plugin-catalog": "^0.7.10", +- "@backstage/plugin-catalog-graph": "^0.2.8", +- "@backstage/plugin-catalog-import": "^0.7.10", +- "@backstage/plugin-catalog-react": "^0.6.12", +- "@backstage/plugin-github-actions": "^0.4.33", +- "@backstage/plugin-org": "^0.4.0", +- "@backstage/plugin-scaffolder": "^0.12.0", +- "@backstage/plugin-search": "^0.6.0", +- "@backstage/plugin-tech-radar": "^0.5.3", +- "@backstage/plugin-techdocs": "^0.13.1", +- "@backstage/plugin-user-settings": "^0.3.17", +- "@backstage/theme": "^0.2.14", ++ "@backstage/app-defaults": "^1.4.5-next.1", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/core-app-api": "^1.11.1-next.0", ++ "@backstage/core-components": "^0.13.8-next.1", ++ "@backstage/core-plugin-api": "^1.8.0-next.0", ++ "@backstage/integration-react": "^1.1.21-next.1", ++ "@backstage/plugin-api-docs": "^0.9.13-next.1", ++ "@backstage/plugin-catalog": "^1.15.0-next.1", ++ "@backstage/plugin-catalog-common": "^1.0.17", ++ "@backstage/plugin-catalog-graph": "^0.2.38-next.1", ++ "@backstage/plugin-catalog-import": "^0.10.2-next.1", ++ "@backstage/plugin-catalog-react": "^1.9.0-next.1", ++ "@backstage/plugin-github-actions": "^0.6.7-next.1", ++ "@backstage/plugin-org": "^0.6.16-next.1", ++ "@backstage/plugin-permission-react": "^0.4.17-next.0", ++ "@backstage/plugin-scaffolder": "^1.16.0-next.1", ++ "@backstage/plugin-search": "^1.4.2-next.1", ++ "@backstage/plugin-search-react": "^1.7.2-next.1", ++ "@backstage/plugin-tech-radar": "^0.6.10-next.1", ++ "@backstage/plugin-techdocs": "^1.8.1-next.1", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.2-next.1", ++ "@backstage/plugin-techdocs-react": "^1.1.13-next.1", ++ "@backstage/plugin-user-settings": "^0.7.12-next.1", ++ "@backstage/theme": "^0.4.4-next.0", + "@material-ui/core": "^4.12.2", +@@ -31,29 +46,15 @@ + "react-dom": "^17.0.2", +- "react-router": "6.0.0-beta.0", +- "react-router-dom": "6.0.0-beta.0", +- "react-use": "^15.3.3" ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^0.2.3", ++ "@backstage/test-utils": "^1.4.5-next.0", ++ "@playwright/test": "^1.32.3", + "@testing-library/jest-dom": "^5.10.1", +- "@testing-library/react": "^10.4.1", +- "@testing-library/user-event": "^12.0.7", +- "@types/jest": "^26.0.7", +- "@types/node": "^14.14.32", ++ "@testing-library/react": "^12.1.3", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^8.0.0", + "@types/react-dom": "*", +- "cross-env": "^7.0.0", +- "cypress": "^7.3.0", +- "eslint-plugin-cypress": "^2.10.3", +- "start-server-and-test": "^1.10.11" +- }, +- "scripts": { +- "start": "backstage-cli app:serve", +- "build": "backstage-cli app:build", +- "clean": "backstage-cli clean", +- "test": "backstage-cli test", +- "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", +- "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", +- "lint": "backstage-cli lint", +- "cy:dev": "cypress open", +- "cy:run": "cypress run" ++ "cross-env": "^7.0.0" + }, +diff --git a/packages/app/public/index.html b/packages/app/public/index.html +index a936c73..fc758ee 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -10,3 +10,2 @@ + /> +- + +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 78949b0..8d62f29 100644 +--- a/packages/app/src/App.tsx ++++ b/packages/app/src/App.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { Navigate, Route } from 'react-router'; ++import { Navigate, Route } from 'react-router-dom'; + import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; +@@ -17,3 +17,2 @@ import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -22,2 +21,4 @@ import { + } from '@backstage/plugin-techdocs'; ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; + import { UserSettingsPage } from '@backstage/plugin-user-settings'; +@@ -30,3 +31,6 @@ import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; + import { createApp } from '@backstage/app-defaults'; +-import { FlatRoutes } from '@backstage/core-app-api'; ++import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; ++import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; ++import { RequirePermission } from '@backstage/plugin-permission-react'; ++import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; + +@@ -38,2 +42,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -44,2 +49,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -51,8 +57,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -64,5 +67,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -77,3 +82,10 @@ const routes = ( + /> +- } /> ++ ++ ++ ++ } ++ /> + }> +@@ -82,2 +94,3 @@ const routes = ( + } /> ++ } /> + +@@ -85,4 +98,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -92,5 +105,3 @@ const App = () => ( + +- ++ , + ); +- +-export default App; +diff --git a/packages/app/src/components/Root/LogoFull.tsx b/packages/app/src/components/Root/LogoFull.tsx +index c7b1c84..47e3b73 100644 +--- a/packages/app/src/components/Root/LogoFull.tsx ++++ b/packages/app/src/components/Root/LogoFull.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/LogoIcon.tsx b/packages/app/src/components/Root/LogoIcon.tsx +index 073cf6e..7eae8c7 100644 +--- a/packages/app/src/components/Root/LogoIcon.tsx ++++ b/packages/app/src/components/Root/LogoIcon.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx +index 7e98c7d..6768b48 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,19 +1,3 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- +-import React, { useContext, PropsWithChildren } from 'react'; +-import { Link, makeStyles } from '@material-ui/core'; ++import React, { PropsWithChildren } from 'react'; ++import { makeStyles } from '@material-ui/core'; + import HomeIcon from '@material-ui/icons/Home'; +@@ -25,3 +9,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -30,6 +13,3 @@ import { + } from '@backstage/plugin-user-settings'; +-import { +- SidebarSearchModal, +- SearchContextProvider, +-} from '@backstage/plugin-search'; ++import { SidebarSearchModal } from '@backstage/plugin-search'; + import { +@@ -37,3 +17,2 @@ import { + sidebarConfig, +- SidebarContext, + SidebarDivider, +@@ -44,2 +23,4 @@ import { + SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -65,3 +46,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -69,8 +50,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -86,5 +62,3 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + } to="/search"> +- +- +- {' '} ++ + +diff --git a/packages/app/src/components/Root/index.ts b/packages/app/src/components/Root/index.ts +index dff706f..3528881 100644 +--- a/packages/app/src/components/Root/index.ts ++++ b/packages/app/src/components/Root/index.ts +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + export { Root } from './Root'; +diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx +index 84d0944..6722ea2 100644 +--- a/packages/app/src/components/catalog/EntityPage.tsx ++++ b/packages/app/src/components/catalog/EntityPage.tsx +@@ -1,16 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ + import React from 'react'; +@@ -42,2 +27,4 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +@@ -70,2 +57,13 @@ import { + ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); ++ + const cicdContent = ( +@@ -107,2 +105,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -169,3 +175,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -196,3 +202,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -215,3 +221,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -315,3 +321,6 @@ const systemPage = ( + +- ++ ++ ++ ++ + +diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx +index a88e725..1788dde 100644 +--- a/packages/app/src/components/search/SearchPage.tsx ++++ b/packages/app/src/components/search/SearchPage.tsx +@@ -1,5 +1,5 @@ + import React from 'react'; +-import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; ++import { makeStyles, Theme, Grid, Paper } from '@material-ui/core'; + +-import { CatalogResultListItem } from '@backstage/plugin-catalog'; ++import { CatalogSearchResultListItem } from '@backstage/plugin-catalog'; + import { +@@ -8,4 +8,5 @@ import { + } from '@backstage/plugin-catalog-react'; +-import { DocsResultListItem } from '@backstage/plugin-techdocs'; ++import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; + ++import { SearchType } from '@backstage/plugin-search'; + import { +@@ -14,6 +15,5 @@ import { + SearchResult, +- SearchType, +- DefaultResultListItem, ++ SearchPagination, + useSearch, +-} from '@backstage/plugin-search'; ++} from '@backstage/plugin-search-react'; + import { +@@ -111,32 +111,6 @@ const SearchPage = () => { + ++ + +- {({ results }) => ( +- +- {results.map(({ type, document }) => { +- switch (type) { +- case 'software-catalog': +- return ( +- +- ); +- case 'techdocs': +- return ( +- +- ); +- default: +- return ( +- +- ); +- } +- })} +- +- )} ++ } /> ++ } /> + +diff --git a/packages/backend/.eslintrc.js b/packages/backend/.eslintrc.js +index 16a033d..e2a53a6 100644 +--- a/packages/backend/.eslintrc.js ++++ b/packages/backend/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint.backend')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index 31231a3..18548e9 100644 +--- a/packages/backend/Dockerfile ++++ b/packages/backend/Dockerfile +@@ -7,3 +7,3 @@ + # yarn tsc +-# yarn build ++# yarn build:backend + # +@@ -11,6 +11,29 @@ + +-FROM node:14-buster-slim ++FROM node:18-bookworm-slim + ++# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends python3 g++ build-essential && \ ++ yarn config set python /usr/bin/python3 ++ ++# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, ++# in which case you should also move better-sqlite3 to "devDependencies" in package.json. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends libsqlite3-dev ++ ++# From here on we use the least-privileged `node` user to run the backend. ++USER node ++ ++# This should create the app dir as `node`. ++# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. ++# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. + WORKDIR /app + ++# This switches many Node.js dependencies to production mode. ++ENV NODE_ENV production ++ + # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. +@@ -18,11 +41,12 @@ WORKDIR /app + # and along with yarn.lock and the root package.json, that's enough to run yarn install. +-COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ ++COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ + RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz + +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + + # Then copy the rest of the backend bundle, along with any other files we might want. +-COPY packages/backend/dist/bundle.tar.gz app-config.yaml ./ ++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"] +diff --git a/packages/backend/README.md b/packages/backend/README.md +index 02426ef..3607b0a 100644 +--- a/packages/backend/README.md ++++ b/packages/backend/README.md +@@ -18,4 +18,2 @@ To run the example backend, first go to the project root and run + yarn install +-yarn tsc +-yarn build + ``` +@@ -27,7 +25,2 @@ After that, go to the `packages/backend` directory and run + ```bash +-AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x \ +-AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x \ +-AUTH_OAUTH2_CLIENT_ID=x AUTH_OAUTH2_CLIENT_SECRET=x \ +-AUTH_OAUTH2_AUTH_URL=x AUTH_OAUTH2_TOKEN_URL=x \ +-LOG_LEVEL=debug \ + yarn start +@@ -35,4 +28,4 @@ yarn start + +-Substitute `x` for actual values, or leave them as dummy values just to try out +-the backend without using the auth or sentry features. ++If you want to override any configuration locally, for example adding any secrets, ++you can do so in `app-config.local.yaml`. + +@@ -45,3 +38,3 @@ locations to the backend. These are places where the backend can find some + entity descriptor data to consume and serve. For more information, see +-[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog). ++[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). + +@@ -65,2 +58,2 @@ and + - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +-- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) ++- [Backstage Documentation](https://backstage.io/docs) +diff --git a/packages/backend/package.json b/packages/backend/package.json +index a5ff8d4..f224eaf 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,30 +6,33 @@ + "private": true, ++ "backstage": { ++ "role": "backend" ++ }, + "scripts": { +- "build": "backstage-cli backend:bundle", +- "build-image": "docker build ../.. -f Dockerfile --tag backstage", +- "start": "backstage-cli backend:dev", +- "lint": "backstage-cli lint", +- "test": "backstage-cli test", +- "clean": "backstage-cli clean", +- "migrate:create": "knex migrate:make -x ts" ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "lint": "backstage-cli package lint", ++ "test": "backstage-cli package test", ++ "clean": "backstage-cli package clean", ++ "build-image": "docker build ../.. -f Dockerfile --tag backstage" + }, + "dependencies": { +- "app": "0.0.0", +- "@backstage/backend-common": "^0.10.5", +- "@backstage/backend-tasks": "^0.1.4", +- "@backstage/catalog-model": "^0.9.10", +- "@backstage/catalog-client": "^0.5.5", +- "@backstage/config": "^0.1.13", +- "@backstage/plugin-app-backend": "^0.3.22", +- "@backstage/plugin-auth-backend": "^0.8.0", +- "@backstage/plugin-catalog-backend": "^0.21.1", +- "@backstage/plugin-permission-common": "^0.4.0", +- "@backstage/plugin-permission-node": "^0.4.1", +- "@backstage/plugin-proxy-backend": "^0.2.16", +- "@backstage/plugin-scaffolder-backend": "^0.15.22", +- "@backstage/plugin-search-backend": "^0.4.0", +- "@backstage/plugin-search-backend-node": "^0.4.5", +- "@backstage/plugin-techdocs-backend": "^0.13.1", +- "@gitbeaker/node": "^34.6.0", +- "@octokit/rest": "^18.5.3", ++ "@backstage/backend-common": "^0.19.9-next.1", ++ "@backstage/backend-tasks": "^0.5.12-next.1", ++ "@backstage/catalog-client": "^1.4.5", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/config": "^1.1.1", ++ "@backstage/plugin-app-backend": "^0.3.55-next.1", ++ "@backstage/plugin-auth-backend": "^0.20.0-next.1", ++ "@backstage/plugin-auth-node": "^0.4.1-next.1", ++ "@backstage/plugin-catalog-backend": "^1.15.0-next.1", ++ "@backstage/plugin-permission-common": "^0.7.9", ++ "@backstage/plugin-permission-node": "^0.7.18-next.1", ++ "@backstage/plugin-proxy-backend": "^0.4.5-next.1", ++ "@backstage/plugin-scaffolder-backend": "^1.19.0-next.1", ++ "@backstage/plugin-search-backend": "^1.4.7-next.1", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.16-next.1", ++ "@backstage/plugin-search-backend-node": "^1.2.11-next.1", ++ "@backstage/plugin-techdocs-backend": "^1.9.0-next.1", ++ "app": "link:../app", ++ "better-sqlite3": "^8.0.0", + "dockerode": "^3.3.1", +@@ -37,4 +40,3 @@ + "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "pg": "^8.3.0", + "winston": "^3.2.1" +@@ -42,6 +44,7 @@ + "devDependencies": { +- "@backstage/cli": "^0.13.0", ++ "@backstage/cli": "^0.24.0-next.1", + "@types/dockerode": "^3.3.0", + "@types/express": "^4.17.6", +- "@types/express-serve-static-core": "^4.17.5" ++ "@types/express-serve-static-core": "^4.17.5", ++ "@types/luxon": "^2.0.4" + }, +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 70bc66b..04c4ff9 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -17,3 +17,3 @@ import { + DatabaseManager, +- SingleHostDiscovery, ++ HostDiscovery, + UrlReaders, +@@ -32,2 +32,3 @@ import { PluginEnvironment } from './types'; + import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; + +@@ -36,7 +37,11 @@ function makeCreateEnv(config: Config) { + const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++ const discovery = HostDiscovery.fromConfig(config); + const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++ const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); + const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); ++ const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); ++ ++ const identity = DefaultIdentityClient.create({ ++ discovery, ++ }); + const permissions = ServerPermissionClient.fromConfig(config, { +@@ -63,2 +68,3 @@ function makeCreateEnv(config: Config) { + permissions, ++ identity, + }; +@@ -106,3 +112,3 @@ module.hot?.accept(); + main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); ++ console.error('Backend failed to start up', error); + process.exit(1); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +index 14e19a1..7c37f68 100644 +--- a/packages/backend/src/plugins/app.ts ++++ b/packages/backend/src/plugins/app.ts +@@ -4,11 +4,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- database, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + return await createRouter({ +- logger, +- config, +- database, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, + appPackageName: 'app', +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +index 5216510..77eb6aa 100644 +--- a/packages/backend/src/plugins/auth.ts ++++ b/packages/backend/src/plugins/auth.ts +@@ -1,2 +1,6 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; ++import { ++ createRouter, ++ providers, ++ defaultAuthProviderFactories, ++} from '@backstage/plugin-auth-backend'; + import { Router } from 'express'; +@@ -4,9 +8,47 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, database, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, ++ providerFactories: { ++ ...defaultAuthProviderFactories, ++ ++ // This replaces the default GitHub auth provider with a customized one. ++ // The `signIn` option enables sign-in for this provider, using the ++ // identity resolution logic that's provided in the `resolver` callback. ++ // ++ // This particular resolver makes all users share a single "guest" identity. ++ // It should only be used for testing and trying out Backstage. ++ // ++ // If you want to use a production ready resolver you can switch to ++ // the one that is commented out below, it looks up a user entity in the ++ // catalog using the GitHub username of the authenticated user. ++ // That resolver requires you to have user entities populated in the catalog, ++ // for example using https://backstage.io/docs/integrations/github/org ++ // ++ // There are other resolvers to choose from, and you can also create ++ // your own, see the auth documentation for more details: ++ // ++ // https://backstage.io/docs/auth/identity-resolver ++ github: providers.github.create({ ++ signIn: { ++ resolver(_, ctx) { ++ const userRef = 'user:default/guest'; // Must be a full entity reference ++ return ctx.issueToken({ ++ claims: { ++ sub: userRef, // The user's own identity ++ ent: [userRef], // A list of identities that the user claims ownership through ++ }, ++ }); ++ }, ++ // resolver: providers.github.resolvers.usernameMatchingUserEntityName(), ++ }, ++ }), ++ }, ++ }); + } +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +index 506f6d9..54ec393 100644 +--- a/packages/backend/src/plugins/proxy.ts ++++ b/packages/backend/src/plugins/proxy.ts +@@ -4,8 +4,10 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ }); + } +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +index 6be2e97..a12fee2 100644 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ b/packages/backend/src/plugins/scaffolder.ts +@@ -1,5 +1,3 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; + import { CatalogClient } from '@backstage/catalog-client'; + import { createRouter } from '@backstage/plugin-scaffolder-backend'; +-import Docker from 'dockerode'; + import { Router } from 'express'; +@@ -7,20 +5,17 @@ import type { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- database, +- reader, +- discovery, +-}: PluginEnvironment): Promise { +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- const catalogClient = new CatalogClient({ discoveryApi: discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ const catalogClient = new CatalogClient({ ++ discoveryApi: env.discovery, ++ }); + + return await createRouter({ +- containerRunner, +- logger, +- config, +- database, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ reader: env.reader, + catalogClient, +- reader, ++ identity: env.identity, ++ permissions: env.permissions, + }); +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +index a0a1cc3..e9469dc 100644 +--- a/packages/backend/src/plugins/search.ts ++++ b/packages/backend/src/plugins/search.ts +@@ -7,15 +7,25 @@ import { + import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend'; ++import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend'; ++import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; ++import { Router } from 'express'; + +-export default async function createPlugin({ +- logger, +- permissions, +- discovery, +- config, +- tokenManager, +-}: PluginEnvironment) { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); ++ const searchEngine = new LunrSearchEngine({ ++ logger: env.logger, ++ }); ++ const indexBuilder = new IndexBuilder({ ++ logger: env.logger, ++ searchEngine, ++ }); ++ ++ const schedule = env.scheduler.createScheduledTaskRunner({ ++ frequency: { minutes: 10 }, ++ timeout: { minutes: 15 }, ++ // A 3 second delay gives the backend server a chance to initialize before ++ // any collators are executed, which may attempt requests against the API. ++ initialDelay: { seconds: 3 }, ++ }); + +@@ -24,6 +34,6 @@ export default async function createPlugin({ + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { +- discovery, +- tokenManager, ++ schedule, ++ factory: DefaultCatalogCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, + }), +@@ -33,7 +43,7 @@ export default async function createPlugin({ + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultTechDocsCollator.fromConfig(config, { +- discovery, +- logger, +- tokenManager, ++ schedule, ++ factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ logger: env.logger, ++ tokenManager: env.tokenManager, + }), +@@ -44,6 +54,4 @@ export default async function createPlugin({ + const { scheduler } = await indexBuilder.build(); ++ scheduler.start(); + +- // A 3 second delay gives the backend server a chance to initialize before +- // any collators are executed, which may attempt requests against the API. +- setTimeout(() => scheduler.start(), 3000); + useHotCleanup(module, () => scheduler.stop()); +@@ -53,5 +61,5 @@ export default async function createPlugin({ + types: indexBuilder.getDocumentTypes(), +- permissions, +- config, +- logger, ++ permissions: env.permissions, ++ config: env.config, ++ logger: env.logger, + }); +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +index 054c64d..be8bb0c 100644 +--- a/packages/backend/src/plugins/techdocs.ts ++++ b/packages/backend/src/plugins/techdocs.ts +@@ -11,13 +11,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +- cache, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, ++ const preparers = await Preparers.fromConfig(env.config, { ++ logger: env.logger, ++ reader: env.reader, + }); +@@ -29,4 +25,4 @@ export default async function createPlugin({ + // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, ++ const generators = await Generators.fromConfig(env.config, { ++ logger: env.logger, + containerRunner, +@@ -37,5 +33,5 @@ export default async function createPlugin({ + // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, ++ const publisher = await Publisher.fromConfig(env.config, { ++ logger: env.logger, ++ discovery: env.discovery, + }); +@@ -49,6 +45,6 @@ export default async function createPlugin({ + publisher, +- logger, +- config, +- discovery, +- cache, ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ cache: env.cache, + }); +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +index 0862b0e..9cd2c74 100644 +--- a/packages/backend/src/types.ts ++++ b/packages/backend/src/types.ts +@@ -10,3 +10,4 @@ import { + import { PluginTaskScheduler } from '@backstage/backend-tasks'; +-import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; ++import { PermissionEvaluator } from '@backstage/plugin-permission-common'; ++import { IdentityApi } from '@backstage/plugin-auth-node'; + +@@ -21,3 +22,4 @@ export type PluginEnvironment = { + scheduler: PluginTaskScheduler; +- permissions: PermissionAuthorizer; ++ permissions: PermissionEvaluator; ++ identity: IdentityApi; + }; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..37c7fb1 +--- /dev/null ++++ b/playwright.config.ts +@@ -0,0 +1,60 @@ ++/* ++ * Copyright 2023 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { defineConfig } from '@playwright/test'; ++import { generateProjects } from '@backstage/e2e-test-utils/playwright'; ++ ++/** ++ * See https://playwright.dev/docs/test-configuration. ++ */ ++export default defineConfig({ ++ timeout: 60_000, ++ ++ expect: { ++ timeout: 5_000, ++ }, ++ ++ // Run your local dev server before starting the tests ++ webServer: process.env.CI ++ ? [] ++ : [ ++ { ++ command: 'yarn start', ++ port: 3000, ++ reuseExistingServer: true, ++ timeout: 60_000, ++ }, ++ ], ++ ++ forbidOnly: !!process.env.CI, ++ ++ retries: process.env.CI ? 2 : 0, ++ ++ reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]], ++ ++ use: { ++ actionTimeout: 0, ++ baseURL: ++ process.env.PLAYWRIGHT_URL ?? ++ (process.env.CI ? 'http://localhost:7007' : 'http://localhost:3000'), ++ screenshot: 'only-on-failure', ++ trace: 'on-first-retry', ++ }, ++ ++ outputDir: 'node_modules/.cache/e2e-test-results', ++ ++ projects: generateProjects(), // Find all packages with e2e-test folders ++}); +diff --git a/plugins/README.md b/plugins/README.md +new file mode 100644 +index 0000000..d7865fd +--- /dev/null ++++ b/plugins/README.md +@@ -0,0 +1,9 @@ ++# The Plugins Folder ++ ++This is where your own plugins and their associated modules live, each in a ++separate folder of its own. ++ ++If you want to create a new plugin here, go to your project root directory, run ++the command `yarn new`, and follow the on-screen instructions. ++ ++You can also check out existing plugins on [the plugin marketplace](https://backstage.io/plugins)! +diff --git a/yarn.lock b/yarn.lock +new file mode 100644 +index 0000000..5ad7fe2 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1 @@ ++# intentionally left empty diff --git a/diffs/0.4.16..0.5.7-next.1.diff b/diffs/0.4.16..0.5.7-next.1.diff new file mode 100644 index 00000000..ef8ee70a --- /dev/null +++ b/diffs/0.4.16..0.5.7-next.1.diff @@ -0,0 +1,1671 @@ +diff --git a/.dockerignore b/.dockerignore +index 63c9c34..05edb62 100644 +--- a/.dockerignore ++++ b/.dockerignore +@@ -1,5 +1,8 @@ + .git ++.yarn/cache ++.yarn/install-state.gz + node_modules +-packages +-!packages/backend/dist ++packages/*/src ++packages/*/node_modules + plugins ++*.local.yaml +diff --git a/.eslintignore b/.eslintignore +new file mode 100644 +index 0000000..e5b1994 +--- /dev/null ++++ b/.eslintignore +@@ -0,0 +1 @@ ++playwright.config.ts +diff --git a/.gitignore b/.gitignore +index d16a8d3..fbf8139 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -17,2 +17,11 @@ node_modules/ + ++# Yarn 3 files ++.pnp.* ++.yarn/* ++!.yarn/patches ++!.yarn/plugins ++!.yarn/releases ++!.yarn/sdks ++!.yarn/versions ++ + # Node version directives +@@ -39 +48,7 @@ site + *-credentials.yaml ++ ++# vscode database functionality support files ++*.session.sql ++ ++# E2E test reports ++e2e-test-report/ +diff --git a/app-config.production.yaml b/app-config.production.yaml +index 5e36c23..8f0751c 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -1,3 +1,3 @@ + app: +- # Should be the same as backend.baseUrl when using the `app-backend` plugin ++ # Should be the same as backend.baseUrl when using the `app-backend` plugin. + baseUrl: http://localhost:7007 +@@ -5,4 +5,31 @@ app: + backend: ++ # Note that the baseUrl should be the URL that the browser and other clients ++ # should use when communicating with the backend, i.e. it needs to be ++ # reachable not just from within the backend host, but from all of your ++ # callers. When its value is "http://localhost:7007", it's strictly private ++ # and can't be reached by others. + baseUrl: http://localhost:7007 +- listen: +- port: 7007 ++ # The listener can also be expressed as a single : string. In this case we bind to ++ # all interfaces, the most permissive setting. The right value depends on your specific deployment. ++ listen: ':7007' ++ ++ # config options: https://node-postgres.com/api/client ++ database: ++ client: pg ++ connection: ++ host: ${POSTGRES_HOST} ++ port: ${POSTGRES_PORT} ++ user: ${POSTGRES_USER} ++ password: ${POSTGRES_PASSWORD} ++ # https://node-postgres.com/features/ssl ++ # you can set the sslmode configuration option via the `PGSSLMODE` environment variable ++ # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require) ++ # ssl: ++ # ca: # if you have a CA file and want to verify it you can uncomment this section ++ # $file: /ca/server.crt ++ ++catalog: ++ # Overrides the default list locations from app-config.yaml as these contain example data. ++ # See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details ++ # on how to get entities into the catalog. ++ locations: [] +diff --git a/app-config.yaml b/app-config.yaml +index 1a622a2..08d2abf 100644 +--- a/app-config.yaml ++++ b/app-config.yaml +@@ -9,3 +9,4 @@ backend: + # Used for enabling authentication, secret is shared by all backend plugins +- # See backend-to-backend-auth.md in the docs for information on the format ++ # See https://backstage.io/docs/auth/service-to-service-auth for ++ # information on the format + # auth: +@@ -16,2 +17,4 @@ backend: + port: 7007 ++ # Uncomment the following host directive to bind to specific interfaces ++ # host: 127.0.0.1 + csp: +@@ -22,9 +25,9 @@ backend: + origin: http://localhost:3000 +- methods: [GET, POST, PUT, DELETE] ++ methods: [GET, HEAD, PATCH, POST, PUT, DELETE] + credentials: true ++ # This is for local development only, it is not recommended to use this in production ++ # The production database configuration is stored in app-config.production.yaml + database: +- client: sqlite3 ++ client: better-sqlite3 + connection: ':memory:' +- cache: +- store: memory + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir +@@ -34,2 +37,4 @@ integrations: + - host: github.com ++ # This is a Personal Access Token or PAT from GitHub. You can find out how to generate this token, and more information ++ # about setting up the GitHub integration here: https://backstage.io/docs/getting-started/configuration#setting-up-a-github-integration + token: ${GITHUB_TOKEN} +@@ -41,5 +46,8 @@ integrations: + proxy: +- '/test': +- target: 'https://example.com' +- changeOrigin: true ++ ### Example for how to add a proxy endpoint for the frontend. ++ ### A typical reason to do this is to handle HTTPS and CORS for internal services. ++ # endpoints: ++ # '/test': ++ # target: 'https://example.com' ++ # changeOrigin: true + +@@ -64,41 +72,32 @@ scaffolder: + catalog: ++ import: ++ entityFilename: catalog-info.yaml ++ pullRequestBranchName: backstage-integration + rules: +- - allow: [Component, System, API, Group, User, Resource, Location] ++ - allow: [Component, System, API, Resource, Location] + locations: +- # Backstage example components +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-components.yaml ++ # Local example data, file locations are relative to the backend process, typically `packages/backend` ++ - type: file ++ target: ../../examples/entities.yaml + +- # Backstage example systems +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-systems.yaml +- +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml +- +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml +- +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.yaml +- +- # Backstage example templates +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/react-ssr-template/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/springboot-grpc-template/template.yaml ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml + rules: + - allow: [Template] +- - type: url +- target: https://github.com/spotify/cookiecutter-golang/blob/master/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/docs-template/template.yaml ++ ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml + rules: +- - allow: [Template] ++ - allow: [User, Group] ++ ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml ++ ++ ## Uncomment these lines to add an example org ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme-corp.yaml ++ # rules: ++ # - allow: [User, Group] +diff --git a/backstage.json b/backstage.json +index 5c3e9d4..707af29 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "0.4.16" ++ "version": "1.20.0-next.1" + } +diff --git a/examples/entities.yaml b/examples/entities.yaml +new file mode 100644 +index 0000000..447e8b1 +--- /dev/null ++++ b/examples/entities.yaml +@@ -0,0 +1,41 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system ++apiVersion: backstage.io/v1alpha1 ++kind: System ++metadata: ++ name: examples ++spec: ++ owner: guests ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: example-website ++spec: ++ type: website ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ providesApis: [example-grpc-api] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api ++apiVersion: backstage.io/v1alpha1 ++kind: API ++metadata: ++ name: example-grpc-api ++spec: ++ type: grpc ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ definition: | ++ syntax = "proto3"; ++ ++ service Exampler { ++ rpc Example (ExampleMessage) returns (ExampleMessage) {}; ++ } ++ ++ message ExampleMessage { ++ string example = 1; ++ }; +diff --git a/examples/org.yaml b/examples/org.yaml +new file mode 100644 +index 0000000..a10e81f +--- /dev/null ++++ b/examples/org.yaml +@@ -0,0 +1,17 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user ++apiVersion: backstage.io/v1alpha1 ++kind: User ++metadata: ++ name: guest ++spec: ++ memberOf: [guests] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group ++apiVersion: backstage.io/v1alpha1 ++kind: Group ++metadata: ++ name: guests ++spec: ++ type: team ++ children: [] +diff --git a/examples/template/content/catalog-info.yaml b/examples/template/content/catalog-info.yaml +new file mode 100644 +index 0000000..d4ccca4 +--- /dev/null ++++ b/examples/template/content/catalog-info.yaml +@@ -0,0 +1,8 @@ ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: ${{ values.name | dump }} ++spec: ++ type: service ++ owner: user:guest ++ lifecycle: experimental +diff --git a/examples/template/content/index.js b/examples/template/content/index.js +new file mode 100644 +index 0000000..071ce5a +--- /dev/null ++++ b/examples/template/content/index.js +@@ -0,0 +1 @@ ++console.log('Hello from ${{ values.name }}!'); +diff --git a/examples/template/content/package.json b/examples/template/content/package.json +new file mode 100644 +index 0000000..86f968a +--- /dev/null ++++ b/examples/template/content/package.json +@@ -0,0 +1,5 @@ ++{ ++ "name": "${{ values.name }}", ++ "private": true, ++ "dependencies": {} ++} +diff --git a/examples/template/template.yaml b/examples/template/template.yaml +new file mode 100644 +index 0000000..33f262b +--- /dev/null ++++ b/examples/template/template.yaml +@@ -0,0 +1,74 @@ ++apiVersion: scaffolder.backstage.io/v1beta3 ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template ++kind: Template ++metadata: ++ name: example-nodejs-template ++ title: Example Node.js Template ++ description: An example template for the scaffolder that creates a simple Node.js service ++spec: ++ owner: user:guest ++ type: service ++ ++ # These parameters are used to generate the input form in the frontend, and are ++ # used to gather input data for the execution of the template. ++ parameters: ++ - title: Fill in some steps ++ required: ++ - name ++ properties: ++ name: ++ title: Name ++ type: string ++ description: Unique name of the component ++ ui:autofocus: true ++ ui:options: ++ rows: 5 ++ - title: Choose a location ++ required: ++ - repoUrl ++ properties: ++ repoUrl: ++ title: Repository Location ++ type: string ++ ui:field: RepoUrlPicker ++ ui:options: ++ allowedHosts: ++ - github.com ++ ++ # These steps are executed in the scaffolder backend, using data that we gathered ++ # via the parameters above. ++ steps: ++ # Each step executes an action, in this case one templates files into the working directory. ++ - id: fetch-base ++ name: Fetch Base ++ action: fetch:template ++ input: ++ url: ./content ++ values: ++ name: ${{ parameters.name }} ++ ++ # This step publishes the contents of the working directory to GitHub. ++ - id: publish ++ name: Publish ++ action: publish:github ++ input: ++ allowedHosts: ['github.com'] ++ description: This is ${{ parameters.name }} ++ repoUrl: ${{ parameters.repoUrl }} ++ ++ # The final step is to register our new component in the catalog. ++ - id: register ++ name: Register ++ action: catalog:register ++ input: ++ repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} ++ catalogInfoPath: '/catalog-info.yaml' ++ ++ # Outputs are displayed to the user after a successful execution of the template. ++ output: ++ links: ++ - title: Repository ++ url: ${{ steps['publish'].output.remoteUrl }} ++ - title: Open in catalog ++ icon: catalog ++ entityRef: ${{ steps['register'].output.entityRef }} +diff --git a/lerna.json b/lerna.json +index 322929d..529a62f 100644 +--- a/lerna.json ++++ b/lerna.json +@@ -3,4 +3,4 @@ + "npmClient": "yarn", +- "useWorkspaces": true, +- "version": "0.1.0" ++ "version": "0.1.0", ++ "$schema": "node_modules/lerna/schemas/lerna-schema.json" + } +diff --git a/package.json b/package.json +index d6ac827..2364da8 100644 +--- a/package.json ++++ b/package.json +@@ -5,3 +5,3 @@ + "engines": { +- "node": "14 || 16" ++ "node": "18 || 20" + }, +@@ -11,3 +11,4 @@ + "start-backend": "yarn workspace backend start", +- "build": "lerna run build", ++ "build:backend": "yarn workspace backend build", ++ "build:all": "backstage-cli repo build --all", + "build-image": "yarn workspace backend build-image", +@@ -15,11 +16,11 @@ + "tsc:full": "tsc --skipLibCheck false --incremental false", +- "clean": "backstage-cli clean && lerna run clean", +- "diff": "lerna run diff --", +- "test": "backstage-cli test", +- "test:all": "lerna run test -- --coverage", +- "lint": "lerna run lint --since origin/master --", +- "lint:all": "lerna run lint --", ++ "clean": "backstage-cli repo clean", ++ "test": "backstage-cli repo test", ++ "test:all": "backstage-cli repo test --coverage", ++ "test:e2e": "playwright test", ++ "fix": "backstage-cli repo fix", ++ "lint": "backstage-cli repo lint --since origin/master", ++ "lint:all": "backstage-cli repo lint", + "prettier:check": "prettier --check .", +- "create-plugin": "backstage-cli create-plugin --scope internal", +- "remove-plugin": "backstage-cli remove-plugin" ++ "new": "backstage-cli new --scope internal" + }, +@@ -32,8 +33,15 @@ + "devDependencies": { +- "@backstage/cli": "^0.13.0", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/e2e-test-utils": "^0.1.0", ++ "@playwright/test": "^1.32.3", + "@spotify/prettier-config": "^12.0.0", +- "concurrently": "^6.0.0", +- "lerna": "^4.0.0", ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^9.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.5.4" ++ "typescript": "~5.2.0" ++ }, ++ "resolutions": { ++ "@types/react": "^17", ++ "@types/react-dom": "^17" + }, +diff --git a/packages/README.md b/packages/README.md +new file mode 100644 +index 0000000..6327fa0 +--- /dev/null ++++ b/packages/README.md +@@ -0,0 +1,9 @@ ++# The Packages Folder ++ ++This is where your own applications and centrally managed libraries live, each ++in a separate folder of its own. ++ ++From the start there's an `app` folder (for the frontend) and a `backend` folder ++(for the Node backend), but you can also add more modules in here that house ++your core additions and adaptations, such as themes, common React component ++libraries, utilities, and similar. +diff --git a/packages/app/.eslintignore b/packages/app/.eslintignore +new file mode 100644 +index 0000000..a48cf0d +--- /dev/null ++++ b/packages/app/.eslintignore +@@ -0,0 +1 @@ ++public +diff --git a/packages/app/.eslintrc.js b/packages/app/.eslintrc.js +index 13573ef..e2a53a6 100644 +--- a/packages/app/.eslintrc.js ++++ b/packages/app/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/app/cypress.json b/packages/app/cypress.json +deleted file mode 100644 +index 5de7ebf..0000000 +--- a/packages/app/cypress.json ++++ /dev/null +@@ -1,5 +0,0 @@ +-{ +- "baseUrl": "http://localhost:3001", +- "fixturesFolder": false, +- "pluginsFile": false +-} +diff --git a/packages/app/cypress/.eslintrc.json b/packages/app/cypress/.eslintrc.json +deleted file mode 100644 +index 2b3a458..0000000 +--- a/packages/app/cypress/.eslintrc.json ++++ /dev/null +@@ -1,21 +0,0 @@ +-{ +- "plugins": ["cypress"], +- "extends": ["plugin:cypress/recommended"], +- "rules": { +- "jest/expect-expect": [ +- "error", +- { +- "assertFunctionNames": ["expect", "cy.contains"] +- } +- ], +- "import/no-extraneous-dependencies": [ +- "error", +- { +- "devDependencies": true, +- "optionalDependencies": true, +- "peerDependencies": true, +- "bundledDependencies": true +- } +- ] +- } +-} +diff --git a/packages/app/cypress/integration/app.js b/packages/app/cypress/integration/app.js +deleted file mode 100644 +index 43fb2e3..0000000 +--- a/packages/app/cypress/integration/app.js ++++ /dev/null +@@ -1,6 +0,0 @@ +-describe('App', () => { +- it('should render the catalog', () => { +- cy.visit('/'); +- cy.contains('My Company Catalog'); +- }); +-}); +diff --git a/packages/app/e2e-tests/app.test.ts b/packages/app/e2e-tests/app.test.ts +new file mode 100644 +index 0000000..d45bc0d +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,23 @@ ++/* ++ * Copyright 2020 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { test, expect } from '@playwright/test'; ++ ++test('App should render the welcome page', async ({ page }) => { ++ await page.goto('/'); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 1681062..2100405 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,23 +5,38 @@ + "bundled": true, ++ "backstage": { ++ "role": "frontend" ++ }, ++ "scripts": { ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "clean": "backstage-cli package clean", ++ "test": "backstage-cli package test", ++ "lint": "backstage-cli package lint" ++ }, + "dependencies": { +- "@backstage/app-defaults": "^0.1.5", +- "@backstage/catalog-model": "^0.9.10", +- "@backstage/cli": "^0.13.0", +- "@backstage/core-app-api": "^0.5.1", +- "@backstage/core-components": "^0.8.6", +- "@backstage/core-plugin-api": "^0.6.0", +- "@backstage/integration-react": "^0.1.19", +- "@backstage/plugin-api-docs": "^0.7.0", +- "@backstage/plugin-catalog": "^0.7.10", +- "@backstage/plugin-catalog-graph": "^0.2.8", +- "@backstage/plugin-catalog-import": "^0.7.10", +- "@backstage/plugin-catalog-react": "^0.6.12", +- "@backstage/plugin-github-actions": "^0.4.33", +- "@backstage/plugin-org": "^0.4.0", +- "@backstage/plugin-scaffolder": "^0.12.0", +- "@backstage/plugin-search": "^0.6.0", +- "@backstage/plugin-tech-radar": "^0.5.3", +- "@backstage/plugin-techdocs": "^0.13.1", +- "@backstage/plugin-user-settings": "^0.3.17", +- "@backstage/theme": "^0.2.14", ++ "@backstage/app-defaults": "^1.4.5-next.1", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/core-app-api": "^1.11.1-next.0", ++ "@backstage/core-components": "^0.13.8-next.1", ++ "@backstage/core-plugin-api": "^1.8.0-next.0", ++ "@backstage/integration-react": "^1.1.21-next.1", ++ "@backstage/plugin-api-docs": "^0.9.13-next.1", ++ "@backstage/plugin-catalog": "^1.15.0-next.1", ++ "@backstage/plugin-catalog-common": "^1.0.17", ++ "@backstage/plugin-catalog-graph": "^0.2.38-next.1", ++ "@backstage/plugin-catalog-import": "^0.10.2-next.1", ++ "@backstage/plugin-catalog-react": "^1.9.0-next.1", ++ "@backstage/plugin-github-actions": "^0.6.7-next.1", ++ "@backstage/plugin-org": "^0.6.16-next.1", ++ "@backstage/plugin-permission-react": "^0.4.17-next.0", ++ "@backstage/plugin-scaffolder": "^1.16.0-next.1", ++ "@backstage/plugin-search": "^1.4.2-next.1", ++ "@backstage/plugin-search-react": "^1.7.2-next.1", ++ "@backstage/plugin-tech-radar": "^0.6.10-next.1", ++ "@backstage/plugin-techdocs": "^1.8.1-next.1", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.2-next.1", ++ "@backstage/plugin-techdocs-react": "^1.1.13-next.1", ++ "@backstage/plugin-user-settings": "^0.7.12-next.1", ++ "@backstage/theme": "^0.4.4-next.0", + "@material-ui/core": "^4.12.2", +@@ -31,29 +46,15 @@ + "react-dom": "^17.0.2", +- "react-router": "6.0.0-beta.0", +- "react-router-dom": "6.0.0-beta.0", +- "react-use": "^15.3.3" ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^0.2.3", ++ "@backstage/test-utils": "^1.4.5-next.0", ++ "@playwright/test": "^1.32.3", + "@testing-library/jest-dom": "^5.10.1", +- "@testing-library/react": "^10.4.1", +- "@testing-library/user-event": "^12.0.7", +- "@types/jest": "^26.0.7", +- "@types/node": "^14.14.32", ++ "@testing-library/react": "^12.1.3", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^8.0.0", + "@types/react-dom": "*", +- "cross-env": "^7.0.0", +- "cypress": "^7.3.0", +- "eslint-plugin-cypress": "^2.10.3", +- "start-server-and-test": "^1.10.11" +- }, +- "scripts": { +- "start": "backstage-cli app:serve", +- "build": "backstage-cli app:build", +- "clean": "backstage-cli clean", +- "test": "backstage-cli test", +- "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", +- "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", +- "lint": "backstage-cli lint", +- "cy:dev": "cypress open", +- "cy:run": "cypress run" ++ "cross-env": "^7.0.0" + }, +diff --git a/packages/app/public/index.html b/packages/app/public/index.html +index a936c73..fc758ee 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -10,3 +10,2 @@ + /> +- + +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 5a379b5..8d62f29 100644 +--- a/packages/app/src/App.tsx ++++ b/packages/app/src/App.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { Navigate, Route } from 'react-router'; ++import { Navigate, Route } from 'react-router-dom'; + import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; +@@ -17,3 +17,2 @@ import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -22,2 +21,4 @@ import { + } from '@backstage/plugin-techdocs'; ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; + import { UserSettingsPage } from '@backstage/plugin-user-settings'; +@@ -30,4 +31,6 @@ import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; + import { createApp } from '@backstage/app-defaults'; +-import { FlatRoutes } from '@backstage/core-app-api'; ++import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; + import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; ++import { RequirePermission } from '@backstage/plugin-permission-react'; ++import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; + +@@ -39,2 +42,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -45,2 +49,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -52,8 +57,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -65,5 +67,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -78,3 +82,10 @@ const routes = ( + /> +- } /> ++ ++ ++ ++ } ++ /> + }> +@@ -87,4 +98,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -94,5 +105,3 @@ const App = () => ( + +- ++ , + ); +- +-export default App; +diff --git a/packages/app/src/components/Root/LogoFull.tsx b/packages/app/src/components/Root/LogoFull.tsx +index c7b1c84..47e3b73 100644 +--- a/packages/app/src/components/Root/LogoFull.tsx ++++ b/packages/app/src/components/Root/LogoFull.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/LogoIcon.tsx b/packages/app/src/components/Root/LogoIcon.tsx +index 073cf6e..7eae8c7 100644 +--- a/packages/app/src/components/Root/LogoIcon.tsx ++++ b/packages/app/src/components/Root/LogoIcon.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx +index 7e98c7d..6768b48 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,19 +1,3 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- +-import React, { useContext, PropsWithChildren } from 'react'; +-import { Link, makeStyles } from '@material-ui/core'; ++import React, { PropsWithChildren } from 'react'; ++import { makeStyles } from '@material-ui/core'; + import HomeIcon from '@material-ui/icons/Home'; +@@ -25,3 +9,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -30,6 +13,3 @@ import { + } from '@backstage/plugin-user-settings'; +-import { +- SidebarSearchModal, +- SearchContextProvider, +-} from '@backstage/plugin-search'; ++import { SidebarSearchModal } from '@backstage/plugin-search'; + import { +@@ -37,3 +17,2 @@ import { + sidebarConfig, +- SidebarContext, + SidebarDivider, +@@ -44,2 +23,4 @@ import { + SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -65,3 +46,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -69,8 +50,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -86,5 +62,3 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + } to="/search"> +- +- +- {' '} ++ + +diff --git a/packages/app/src/components/Root/index.ts b/packages/app/src/components/Root/index.ts +index dff706f..3528881 100644 +--- a/packages/app/src/components/Root/index.ts ++++ b/packages/app/src/components/Root/index.ts +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + export { Root } from './Root'; +diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx +index 84d0944..6722ea2 100644 +--- a/packages/app/src/components/catalog/EntityPage.tsx ++++ b/packages/app/src/components/catalog/EntityPage.tsx +@@ -1,16 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ + import React from 'react'; +@@ -42,2 +27,4 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +@@ -70,2 +57,13 @@ import { + ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); ++ + const cicdContent = ( +@@ -107,2 +105,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -169,3 +175,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -196,3 +202,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -215,3 +221,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -315,3 +321,6 @@ const systemPage = ( + +- ++ ++ ++ ++ + +diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx +index a88e725..1788dde 100644 +--- a/packages/app/src/components/search/SearchPage.tsx ++++ b/packages/app/src/components/search/SearchPage.tsx +@@ -1,5 +1,5 @@ + import React from 'react'; +-import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; ++import { makeStyles, Theme, Grid, Paper } from '@material-ui/core'; + +-import { CatalogResultListItem } from '@backstage/plugin-catalog'; ++import { CatalogSearchResultListItem } from '@backstage/plugin-catalog'; + import { +@@ -8,4 +8,5 @@ import { + } from '@backstage/plugin-catalog-react'; +-import { DocsResultListItem } from '@backstage/plugin-techdocs'; ++import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; + ++import { SearchType } from '@backstage/plugin-search'; + import { +@@ -14,6 +15,5 @@ import { + SearchResult, +- SearchType, +- DefaultResultListItem, ++ SearchPagination, + useSearch, +-} from '@backstage/plugin-search'; ++} from '@backstage/plugin-search-react'; + import { +@@ -111,32 +111,6 @@ const SearchPage = () => { + ++ + +- {({ results }) => ( +- +- {results.map(({ type, document }) => { +- switch (type) { +- case 'software-catalog': +- return ( +- +- ); +- case 'techdocs': +- return ( +- +- ); +- default: +- return ( +- +- ); +- } +- })} +- +- )} ++ } /> ++ } /> + +diff --git a/packages/backend/.eslintrc.js b/packages/backend/.eslintrc.js +index 16a033d..e2a53a6 100644 +--- a/packages/backend/.eslintrc.js ++++ b/packages/backend/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint.backend')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index 31231a3..18548e9 100644 +--- a/packages/backend/Dockerfile ++++ b/packages/backend/Dockerfile +@@ -7,3 +7,3 @@ + # yarn tsc +-# yarn build ++# yarn build:backend + # +@@ -11,6 +11,29 @@ + +-FROM node:14-buster-slim ++FROM node:18-bookworm-slim + ++# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends python3 g++ build-essential && \ ++ yarn config set python /usr/bin/python3 ++ ++# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, ++# in which case you should also move better-sqlite3 to "devDependencies" in package.json. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends libsqlite3-dev ++ ++# From here on we use the least-privileged `node` user to run the backend. ++USER node ++ ++# This should create the app dir as `node`. ++# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. ++# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. + WORKDIR /app + ++# This switches many Node.js dependencies to production mode. ++ENV NODE_ENV production ++ + # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. +@@ -18,11 +41,12 @@ WORKDIR /app + # and along with yarn.lock and the root package.json, that's enough to run yarn install. +-COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ ++COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ + RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz + +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + + # Then copy the rest of the backend bundle, along with any other files we might want. +-COPY packages/backend/dist/bundle.tar.gz app-config.yaml ./ ++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"] +diff --git a/packages/backend/README.md b/packages/backend/README.md +index 02426ef..3607b0a 100644 +--- a/packages/backend/README.md ++++ b/packages/backend/README.md +@@ -18,4 +18,2 @@ To run the example backend, first go to the project root and run + yarn install +-yarn tsc +-yarn build + ``` +@@ -27,7 +25,2 @@ After that, go to the `packages/backend` directory and run + ```bash +-AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x \ +-AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x \ +-AUTH_OAUTH2_CLIENT_ID=x AUTH_OAUTH2_CLIENT_SECRET=x \ +-AUTH_OAUTH2_AUTH_URL=x AUTH_OAUTH2_TOKEN_URL=x \ +-LOG_LEVEL=debug \ + yarn start +@@ -35,4 +28,4 @@ yarn start + +-Substitute `x` for actual values, or leave them as dummy values just to try out +-the backend without using the auth or sentry features. ++If you want to override any configuration locally, for example adding any secrets, ++you can do so in `app-config.local.yaml`. + +@@ -45,3 +38,3 @@ locations to the backend. These are places where the backend can find some + entity descriptor data to consume and serve. For more information, see +-[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog). ++[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). + +@@ -65,2 +58,2 @@ and + - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +-- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) ++- [Backstage Documentation](https://backstage.io/docs) +diff --git a/packages/backend/package.json b/packages/backend/package.json +index a5ff8d4..f224eaf 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,30 +6,33 @@ + "private": true, ++ "backstage": { ++ "role": "backend" ++ }, + "scripts": { +- "build": "backstage-cli backend:bundle", +- "build-image": "docker build ../.. -f Dockerfile --tag backstage", +- "start": "backstage-cli backend:dev", +- "lint": "backstage-cli lint", +- "test": "backstage-cli test", +- "clean": "backstage-cli clean", +- "migrate:create": "knex migrate:make -x ts" ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "lint": "backstage-cli package lint", ++ "test": "backstage-cli package test", ++ "clean": "backstage-cli package clean", ++ "build-image": "docker build ../.. -f Dockerfile --tag backstage" + }, + "dependencies": { +- "app": "0.0.0", +- "@backstage/backend-common": "^0.10.5", +- "@backstage/backend-tasks": "^0.1.4", +- "@backstage/catalog-model": "^0.9.10", +- "@backstage/catalog-client": "^0.5.5", +- "@backstage/config": "^0.1.13", +- "@backstage/plugin-app-backend": "^0.3.22", +- "@backstage/plugin-auth-backend": "^0.8.0", +- "@backstage/plugin-catalog-backend": "^0.21.1", +- "@backstage/plugin-permission-common": "^0.4.0", +- "@backstage/plugin-permission-node": "^0.4.1", +- "@backstage/plugin-proxy-backend": "^0.2.16", +- "@backstage/plugin-scaffolder-backend": "^0.15.22", +- "@backstage/plugin-search-backend": "^0.4.0", +- "@backstage/plugin-search-backend-node": "^0.4.5", +- "@backstage/plugin-techdocs-backend": "^0.13.1", +- "@gitbeaker/node": "^34.6.0", +- "@octokit/rest": "^18.5.3", ++ "@backstage/backend-common": "^0.19.9-next.1", ++ "@backstage/backend-tasks": "^0.5.12-next.1", ++ "@backstage/catalog-client": "^1.4.5", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/config": "^1.1.1", ++ "@backstage/plugin-app-backend": "^0.3.55-next.1", ++ "@backstage/plugin-auth-backend": "^0.20.0-next.1", ++ "@backstage/plugin-auth-node": "^0.4.1-next.1", ++ "@backstage/plugin-catalog-backend": "^1.15.0-next.1", ++ "@backstage/plugin-permission-common": "^0.7.9", ++ "@backstage/plugin-permission-node": "^0.7.18-next.1", ++ "@backstage/plugin-proxy-backend": "^0.4.5-next.1", ++ "@backstage/plugin-scaffolder-backend": "^1.19.0-next.1", ++ "@backstage/plugin-search-backend": "^1.4.7-next.1", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.16-next.1", ++ "@backstage/plugin-search-backend-node": "^1.2.11-next.1", ++ "@backstage/plugin-techdocs-backend": "^1.9.0-next.1", ++ "app": "link:../app", ++ "better-sqlite3": "^8.0.0", + "dockerode": "^3.3.1", +@@ -37,4 +40,3 @@ + "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "pg": "^8.3.0", + "winston": "^3.2.1" +@@ -42,6 +44,7 @@ + "devDependencies": { +- "@backstage/cli": "^0.13.0", ++ "@backstage/cli": "^0.24.0-next.1", + "@types/dockerode": "^3.3.0", + "@types/express": "^4.17.6", +- "@types/express-serve-static-core": "^4.17.5" ++ "@types/express-serve-static-core": "^4.17.5", ++ "@types/luxon": "^2.0.4" + }, +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 70bc66b..04c4ff9 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -17,3 +17,3 @@ import { + DatabaseManager, +- SingleHostDiscovery, ++ HostDiscovery, + UrlReaders, +@@ -32,2 +32,3 @@ import { PluginEnvironment } from './types'; + import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; + +@@ -36,7 +37,11 @@ function makeCreateEnv(config: Config) { + const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++ const discovery = HostDiscovery.fromConfig(config); + const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++ const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); + const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); ++ const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); ++ ++ const identity = DefaultIdentityClient.create({ ++ discovery, ++ }); + const permissions = ServerPermissionClient.fromConfig(config, { +@@ -63,2 +68,3 @@ function makeCreateEnv(config: Config) { + permissions, ++ identity, + }; +@@ -106,3 +112,3 @@ module.hot?.accept(); + main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); ++ console.error('Backend failed to start up', error); + process.exit(1); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +index 14e19a1..7c37f68 100644 +--- a/packages/backend/src/plugins/app.ts ++++ b/packages/backend/src/plugins/app.ts +@@ -4,11 +4,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- database, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + return await createRouter({ +- logger, +- config, +- database, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, + appPackageName: 'app', +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +index 5216510..77eb6aa 100644 +--- a/packages/backend/src/plugins/auth.ts ++++ b/packages/backend/src/plugins/auth.ts +@@ -1,2 +1,6 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; ++import { ++ createRouter, ++ providers, ++ defaultAuthProviderFactories, ++} from '@backstage/plugin-auth-backend'; + import { Router } from 'express'; +@@ -4,9 +8,47 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, database, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, ++ providerFactories: { ++ ...defaultAuthProviderFactories, ++ ++ // This replaces the default GitHub auth provider with a customized one. ++ // The `signIn` option enables sign-in for this provider, using the ++ // identity resolution logic that's provided in the `resolver` callback. ++ // ++ // This particular resolver makes all users share a single "guest" identity. ++ // It should only be used for testing and trying out Backstage. ++ // ++ // If you want to use a production ready resolver you can switch to ++ // the one that is commented out below, it looks up a user entity in the ++ // catalog using the GitHub username of the authenticated user. ++ // That resolver requires you to have user entities populated in the catalog, ++ // for example using https://backstage.io/docs/integrations/github/org ++ // ++ // There are other resolvers to choose from, and you can also create ++ // your own, see the auth documentation for more details: ++ // ++ // https://backstage.io/docs/auth/identity-resolver ++ github: providers.github.create({ ++ signIn: { ++ resolver(_, ctx) { ++ const userRef = 'user:default/guest'; // Must be a full entity reference ++ return ctx.issueToken({ ++ claims: { ++ sub: userRef, // The user's own identity ++ ent: [userRef], // A list of identities that the user claims ownership through ++ }, ++ }); ++ }, ++ // resolver: providers.github.resolvers.usernameMatchingUserEntityName(), ++ }, ++ }), ++ }, ++ }); + } +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +index 506f6d9..54ec393 100644 +--- a/packages/backend/src/plugins/proxy.ts ++++ b/packages/backend/src/plugins/proxy.ts +@@ -4,8 +4,10 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ }); + } +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +index 6be2e97..a12fee2 100644 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ b/packages/backend/src/plugins/scaffolder.ts +@@ -1,5 +1,3 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; + import { CatalogClient } from '@backstage/catalog-client'; + import { createRouter } from '@backstage/plugin-scaffolder-backend'; +-import Docker from 'dockerode'; + import { Router } from 'express'; +@@ -7,20 +5,17 @@ import type { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- database, +- reader, +- discovery, +-}: PluginEnvironment): Promise { +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- const catalogClient = new CatalogClient({ discoveryApi: discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ const catalogClient = new CatalogClient({ ++ discoveryApi: env.discovery, ++ }); + + return await createRouter({ +- containerRunner, +- logger, +- config, +- database, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ reader: env.reader, + catalogClient, +- reader, ++ identity: env.identity, ++ permissions: env.permissions, + }); +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +index a0a1cc3..e9469dc 100644 +--- a/packages/backend/src/plugins/search.ts ++++ b/packages/backend/src/plugins/search.ts +@@ -7,15 +7,25 @@ import { + import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend'; ++import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend'; ++import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; ++import { Router } from 'express'; + +-export default async function createPlugin({ +- logger, +- permissions, +- discovery, +- config, +- tokenManager, +-}: PluginEnvironment) { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); ++ const searchEngine = new LunrSearchEngine({ ++ logger: env.logger, ++ }); ++ const indexBuilder = new IndexBuilder({ ++ logger: env.logger, ++ searchEngine, ++ }); ++ ++ const schedule = env.scheduler.createScheduledTaskRunner({ ++ frequency: { minutes: 10 }, ++ timeout: { minutes: 15 }, ++ // A 3 second delay gives the backend server a chance to initialize before ++ // any collators are executed, which may attempt requests against the API. ++ initialDelay: { seconds: 3 }, ++ }); + +@@ -24,6 +34,6 @@ export default async function createPlugin({ + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { +- discovery, +- tokenManager, ++ schedule, ++ factory: DefaultCatalogCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, + }), +@@ -33,7 +43,7 @@ export default async function createPlugin({ + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultTechDocsCollator.fromConfig(config, { +- discovery, +- logger, +- tokenManager, ++ schedule, ++ factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ logger: env.logger, ++ tokenManager: env.tokenManager, + }), +@@ -44,6 +54,4 @@ export default async function createPlugin({ + const { scheduler } = await indexBuilder.build(); ++ scheduler.start(); + +- // A 3 second delay gives the backend server a chance to initialize before +- // any collators are executed, which may attempt requests against the API. +- setTimeout(() => scheduler.start(), 3000); + useHotCleanup(module, () => scheduler.stop()); +@@ -53,5 +61,5 @@ export default async function createPlugin({ + types: indexBuilder.getDocumentTypes(), +- permissions, +- config, +- logger, ++ permissions: env.permissions, ++ config: env.config, ++ logger: env.logger, + }); +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +index 054c64d..be8bb0c 100644 +--- a/packages/backend/src/plugins/techdocs.ts ++++ b/packages/backend/src/plugins/techdocs.ts +@@ -11,13 +11,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +- cache, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, ++ const preparers = await Preparers.fromConfig(env.config, { ++ logger: env.logger, ++ reader: env.reader, + }); +@@ -29,4 +25,4 @@ export default async function createPlugin({ + // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, ++ const generators = await Generators.fromConfig(env.config, { ++ logger: env.logger, + containerRunner, +@@ -37,5 +33,5 @@ export default async function createPlugin({ + // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, ++ const publisher = await Publisher.fromConfig(env.config, { ++ logger: env.logger, ++ discovery: env.discovery, + }); +@@ -49,6 +45,6 @@ export default async function createPlugin({ + publisher, +- logger, +- config, +- discovery, +- cache, ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ cache: env.cache, + }); +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +index 0862b0e..9cd2c74 100644 +--- a/packages/backend/src/types.ts ++++ b/packages/backend/src/types.ts +@@ -10,3 +10,4 @@ import { + import { PluginTaskScheduler } from '@backstage/backend-tasks'; +-import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; ++import { PermissionEvaluator } from '@backstage/plugin-permission-common'; ++import { IdentityApi } from '@backstage/plugin-auth-node'; + +@@ -21,3 +22,4 @@ export type PluginEnvironment = { + scheduler: PluginTaskScheduler; +- permissions: PermissionAuthorizer; ++ permissions: PermissionEvaluator; ++ identity: IdentityApi; + }; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..37c7fb1 +--- /dev/null ++++ b/playwright.config.ts +@@ -0,0 +1,60 @@ ++/* ++ * Copyright 2023 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { defineConfig } from '@playwright/test'; ++import { generateProjects } from '@backstage/e2e-test-utils/playwright'; ++ ++/** ++ * See https://playwright.dev/docs/test-configuration. ++ */ ++export default defineConfig({ ++ timeout: 60_000, ++ ++ expect: { ++ timeout: 5_000, ++ }, ++ ++ // Run your local dev server before starting the tests ++ webServer: process.env.CI ++ ? [] ++ : [ ++ { ++ command: 'yarn start', ++ port: 3000, ++ reuseExistingServer: true, ++ timeout: 60_000, ++ }, ++ ], ++ ++ forbidOnly: !!process.env.CI, ++ ++ retries: process.env.CI ? 2 : 0, ++ ++ reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]], ++ ++ use: { ++ actionTimeout: 0, ++ baseURL: ++ process.env.PLAYWRIGHT_URL ?? ++ (process.env.CI ? 'http://localhost:7007' : 'http://localhost:3000'), ++ screenshot: 'only-on-failure', ++ trace: 'on-first-retry', ++ }, ++ ++ outputDir: 'node_modules/.cache/e2e-test-results', ++ ++ projects: generateProjects(), // Find all packages with e2e-test folders ++}); +diff --git a/plugins/README.md b/plugins/README.md +new file mode 100644 +index 0000000..d7865fd +--- /dev/null ++++ b/plugins/README.md +@@ -0,0 +1,9 @@ ++# The Plugins Folder ++ ++This is where your own plugins and their associated modules live, each in a ++separate folder of its own. ++ ++If you want to create a new plugin here, go to your project root directory, run ++the command `yarn new`, and follow the on-screen instructions. ++ ++You can also check out existing plugins on [the plugin marketplace](https://backstage.io/plugins)! +diff --git a/yarn.lock b/yarn.lock +new file mode 100644 +index 0000000..5ad7fe2 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1 @@ ++# intentionally left empty diff --git a/diffs/0.4.18..0.5.7-next.1.diff b/diffs/0.4.18..0.5.7-next.1.diff new file mode 100644 index 00000000..0cf5eae1 --- /dev/null +++ b/diffs/0.4.18..0.5.7-next.1.diff @@ -0,0 +1,1675 @@ +diff --git a/.dockerignore b/.dockerignore +index 63c9c34..05edb62 100644 +--- a/.dockerignore ++++ b/.dockerignore +@@ -1,5 +1,8 @@ + .git ++.yarn/cache ++.yarn/install-state.gz + node_modules +-packages +-!packages/backend/dist ++packages/*/src ++packages/*/node_modules + plugins ++*.local.yaml +diff --git a/.eslintignore b/.eslintignore +new file mode 100644 +index 0000000..e5b1994 +--- /dev/null ++++ b/.eslintignore +@@ -0,0 +1 @@ ++playwright.config.ts +diff --git a/.gitignore b/.gitignore +index d16a8d3..fbf8139 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -17,2 +17,11 @@ node_modules/ + ++# Yarn 3 files ++.pnp.* ++.yarn/* ++!.yarn/patches ++!.yarn/plugins ++!.yarn/releases ++!.yarn/sdks ++!.yarn/versions ++ + # Node version directives +@@ -39 +48,7 @@ site + *-credentials.yaml ++ ++# vscode database functionality support files ++*.session.sql ++ ++# E2E test reports ++e2e-test-report/ +diff --git a/app-config.production.yaml b/app-config.production.yaml +index 5e36c23..8f0751c 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -1,3 +1,3 @@ + app: +- # Should be the same as backend.baseUrl when using the `app-backend` plugin ++ # Should be the same as backend.baseUrl when using the `app-backend` plugin. + baseUrl: http://localhost:7007 +@@ -5,4 +5,31 @@ app: + backend: ++ # Note that the baseUrl should be the URL that the browser and other clients ++ # should use when communicating with the backend, i.e. it needs to be ++ # reachable not just from within the backend host, but from all of your ++ # callers. When its value is "http://localhost:7007", it's strictly private ++ # and can't be reached by others. + baseUrl: http://localhost:7007 +- listen: +- port: 7007 ++ # The listener can also be expressed as a single : string. In this case we bind to ++ # all interfaces, the most permissive setting. The right value depends on your specific deployment. ++ listen: ':7007' ++ ++ # config options: https://node-postgres.com/api/client ++ database: ++ client: pg ++ connection: ++ host: ${POSTGRES_HOST} ++ port: ${POSTGRES_PORT} ++ user: ${POSTGRES_USER} ++ password: ${POSTGRES_PASSWORD} ++ # https://node-postgres.com/features/ssl ++ # you can set the sslmode configuration option via the `PGSSLMODE` environment variable ++ # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require) ++ # ssl: ++ # ca: # if you have a CA file and want to verify it you can uncomment this section ++ # $file: /ca/server.crt ++ ++catalog: ++ # Overrides the default list locations from app-config.yaml as these contain example data. ++ # See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details ++ # on how to get entities into the catalog. ++ locations: [] +diff --git a/app-config.yaml b/app-config.yaml +index d45e354..08d2abf 100644 +--- a/app-config.yaml ++++ b/app-config.yaml +@@ -9,3 +9,3 @@ backend: + # Used for enabling authentication, secret is shared by all backend plugins +- # See https://backstage.io/docs/tutorials/backend-to-backend-auth for ++ # See https://backstage.io/docs/auth/service-to-service-auth for + # information on the format +@@ -17,2 +17,4 @@ backend: + port: 7007 ++ # Uncomment the following host directive to bind to specific interfaces ++ # host: 127.0.0.1 + csp: +@@ -23,9 +25,9 @@ backend: + origin: http://localhost:3000 +- methods: [GET, POST, PUT, DELETE] ++ methods: [GET, HEAD, PATCH, POST, PUT, DELETE] + credentials: true ++ # This is for local development only, it is not recommended to use this in production ++ # The production database configuration is stored in app-config.production.yaml + database: +- client: sqlite3 ++ client: better-sqlite3 + connection: ':memory:' +- cache: +- store: memory + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir +@@ -35,2 +37,4 @@ integrations: + - host: github.com ++ # This is a Personal Access Token or PAT from GitHub. You can find out how to generate this token, and more information ++ # about setting up the GitHub integration here: https://backstage.io/docs/getting-started/configuration#setting-up-a-github-integration + token: ${GITHUB_TOKEN} +@@ -42,5 +46,8 @@ integrations: + proxy: +- '/test': +- target: 'https://example.com' +- changeOrigin: true ++ ### Example for how to add a proxy endpoint for the frontend. ++ ### A typical reason to do this is to handle HTTPS and CORS for internal services. ++ # endpoints: ++ # '/test': ++ # target: 'https://example.com' ++ # changeOrigin: true + +@@ -69,40 +76,28 @@ catalog: + rules: +- - allow: [Component, System, API, Group, User, Resource, Location] ++ - allow: [Component, System, API, Resource, Location] + locations: +- # Backstage example components +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-components.yaml ++ # Local example data, file locations are relative to the backend process, typically `packages/backend` ++ - type: file ++ target: ../../examples/entities.yaml + +- # Backstage example systems +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-systems.yaml +- +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml +- +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml +- +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.yaml +- +- # Backstage example templates +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/react-ssr-template/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/springboot-grpc-template/template.yaml ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml + rules: + - allow: [Template] +- - type: url +- target: https://github.com/spotify/cookiecutter-golang/blob/master/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/docs-template/template.yaml ++ ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml + rules: +- - allow: [Template] ++ - allow: [User, Group] ++ ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml ++ ++ ## Uncomment these lines to add an example org ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme-corp.yaml ++ # rules: ++ # - allow: [User, Group] +diff --git a/backstage.json b/backstage.json +index 542bc3e..707af29 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "0.4.18" ++ "version": "1.20.0-next.1" + } +diff --git a/examples/entities.yaml b/examples/entities.yaml +new file mode 100644 +index 0000000..447e8b1 +--- /dev/null ++++ b/examples/entities.yaml +@@ -0,0 +1,41 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system ++apiVersion: backstage.io/v1alpha1 ++kind: System ++metadata: ++ name: examples ++spec: ++ owner: guests ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: example-website ++spec: ++ type: website ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ providesApis: [example-grpc-api] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api ++apiVersion: backstage.io/v1alpha1 ++kind: API ++metadata: ++ name: example-grpc-api ++spec: ++ type: grpc ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ definition: | ++ syntax = "proto3"; ++ ++ service Exampler { ++ rpc Example (ExampleMessage) returns (ExampleMessage) {}; ++ } ++ ++ message ExampleMessage { ++ string example = 1; ++ }; +diff --git a/examples/org.yaml b/examples/org.yaml +new file mode 100644 +index 0000000..a10e81f +--- /dev/null ++++ b/examples/org.yaml +@@ -0,0 +1,17 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user ++apiVersion: backstage.io/v1alpha1 ++kind: User ++metadata: ++ name: guest ++spec: ++ memberOf: [guests] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group ++apiVersion: backstage.io/v1alpha1 ++kind: Group ++metadata: ++ name: guests ++spec: ++ type: team ++ children: [] +diff --git a/examples/template/content/catalog-info.yaml b/examples/template/content/catalog-info.yaml +new file mode 100644 +index 0000000..d4ccca4 +--- /dev/null ++++ b/examples/template/content/catalog-info.yaml +@@ -0,0 +1,8 @@ ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: ${{ values.name | dump }} ++spec: ++ type: service ++ owner: user:guest ++ lifecycle: experimental +diff --git a/examples/template/content/index.js b/examples/template/content/index.js +new file mode 100644 +index 0000000..071ce5a +--- /dev/null ++++ b/examples/template/content/index.js +@@ -0,0 +1 @@ ++console.log('Hello from ${{ values.name }}!'); +diff --git a/examples/template/content/package.json b/examples/template/content/package.json +new file mode 100644 +index 0000000..86f968a +--- /dev/null ++++ b/examples/template/content/package.json +@@ -0,0 +1,5 @@ ++{ ++ "name": "${{ values.name }}", ++ "private": true, ++ "dependencies": {} ++} +diff --git a/examples/template/template.yaml b/examples/template/template.yaml +new file mode 100644 +index 0000000..33f262b +--- /dev/null ++++ b/examples/template/template.yaml +@@ -0,0 +1,74 @@ ++apiVersion: scaffolder.backstage.io/v1beta3 ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template ++kind: Template ++metadata: ++ name: example-nodejs-template ++ title: Example Node.js Template ++ description: An example template for the scaffolder that creates a simple Node.js service ++spec: ++ owner: user:guest ++ type: service ++ ++ # These parameters are used to generate the input form in the frontend, and are ++ # used to gather input data for the execution of the template. ++ parameters: ++ - title: Fill in some steps ++ required: ++ - name ++ properties: ++ name: ++ title: Name ++ type: string ++ description: Unique name of the component ++ ui:autofocus: true ++ ui:options: ++ rows: 5 ++ - title: Choose a location ++ required: ++ - repoUrl ++ properties: ++ repoUrl: ++ title: Repository Location ++ type: string ++ ui:field: RepoUrlPicker ++ ui:options: ++ allowedHosts: ++ - github.com ++ ++ # These steps are executed in the scaffolder backend, using data that we gathered ++ # via the parameters above. ++ steps: ++ # Each step executes an action, in this case one templates files into the working directory. ++ - id: fetch-base ++ name: Fetch Base ++ action: fetch:template ++ input: ++ url: ./content ++ values: ++ name: ${{ parameters.name }} ++ ++ # This step publishes the contents of the working directory to GitHub. ++ - id: publish ++ name: Publish ++ action: publish:github ++ input: ++ allowedHosts: ['github.com'] ++ description: This is ${{ parameters.name }} ++ repoUrl: ${{ parameters.repoUrl }} ++ ++ # The final step is to register our new component in the catalog. ++ - id: register ++ name: Register ++ action: catalog:register ++ input: ++ repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} ++ catalogInfoPath: '/catalog-info.yaml' ++ ++ # Outputs are displayed to the user after a successful execution of the template. ++ output: ++ links: ++ - title: Repository ++ url: ${{ steps['publish'].output.remoteUrl }} ++ - title: Open in catalog ++ icon: catalog ++ entityRef: ${{ steps['register'].output.entityRef }} +diff --git a/lerna.json b/lerna.json +index 322929d..529a62f 100644 +--- a/lerna.json ++++ b/lerna.json +@@ -3,4 +3,4 @@ + "npmClient": "yarn", +- "useWorkspaces": true, +- "version": "0.1.0" ++ "version": "0.1.0", ++ "$schema": "node_modules/lerna/schemas/lerna-schema.json" + } +diff --git a/package.json b/package.json +index 5dde784..2364da8 100644 +--- a/package.json ++++ b/package.json +@@ -5,3 +5,3 @@ + "engines": { +- "node": "14 || 16" ++ "node": "18 || 20" + }, +@@ -11,3 +11,4 @@ + "start-backend": "yarn workspace backend start", +- "build": "lerna run build", ++ "build:backend": "yarn workspace backend build", ++ "build:all": "backstage-cli repo build --all", + "build-image": "yarn workspace backend build-image", +@@ -15,11 +16,11 @@ + "tsc:full": "tsc --skipLibCheck false --incremental false", +- "clean": "backstage-cli clean && lerna run clean", +- "diff": "lerna run diff --", +- "test": "backstage-cli test", +- "test:all": "lerna run test -- --coverage", +- "lint": "lerna run lint --since origin/master --", +- "lint:all": "lerna run lint --", ++ "clean": "backstage-cli repo clean", ++ "test": "backstage-cli repo test", ++ "test:all": "backstage-cli repo test --coverage", ++ "test:e2e": "playwright test", ++ "fix": "backstage-cli repo fix", ++ "lint": "backstage-cli repo lint --since origin/master", ++ "lint:all": "backstage-cli repo lint", + "prettier:check": "prettier --check .", +- "create-plugin": "backstage-cli create-plugin --scope internal", +- "remove-plugin": "backstage-cli remove-plugin" ++ "new": "backstage-cli new --scope internal" + }, +@@ -32,8 +33,15 @@ + "devDependencies": { +- "@backstage/cli": "^0.13.1", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/e2e-test-utils": "^0.1.0", ++ "@playwright/test": "^1.32.3", + "@spotify/prettier-config": "^12.0.0", +- "concurrently": "^6.0.0", +- "lerna": "^4.0.0", ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^9.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.5.4" ++ "typescript": "~5.2.0" ++ }, ++ "resolutions": { ++ "@types/react": "^17", ++ "@types/react-dom": "^17" + }, +diff --git a/packages/README.md b/packages/README.md +new file mode 100644 +index 0000000..6327fa0 +--- /dev/null ++++ b/packages/README.md +@@ -0,0 +1,9 @@ ++# The Packages Folder ++ ++This is where your own applications and centrally managed libraries live, each ++in a separate folder of its own. ++ ++From the start there's an `app` folder (for the frontend) and a `backend` folder ++(for the Node backend), but you can also add more modules in here that house ++your core additions and adaptations, such as themes, common React component ++libraries, utilities, and similar. +diff --git a/packages/app/.eslintignore b/packages/app/.eslintignore +new file mode 100644 +index 0000000..a48cf0d +--- /dev/null ++++ b/packages/app/.eslintignore +@@ -0,0 +1 @@ ++public +diff --git a/packages/app/.eslintrc.js b/packages/app/.eslintrc.js +index 13573ef..e2a53a6 100644 +--- a/packages/app/.eslintrc.js ++++ b/packages/app/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/app/cypress.json b/packages/app/cypress.json +deleted file mode 100644 +index 5de7ebf..0000000 +--- a/packages/app/cypress.json ++++ /dev/null +@@ -1,5 +0,0 @@ +-{ +- "baseUrl": "http://localhost:3001", +- "fixturesFolder": false, +- "pluginsFile": false +-} +diff --git a/packages/app/cypress/.eslintrc.json b/packages/app/cypress/.eslintrc.json +deleted file mode 100644 +index 2b3a458..0000000 +--- a/packages/app/cypress/.eslintrc.json ++++ /dev/null +@@ -1,21 +0,0 @@ +-{ +- "plugins": ["cypress"], +- "extends": ["plugin:cypress/recommended"], +- "rules": { +- "jest/expect-expect": [ +- "error", +- { +- "assertFunctionNames": ["expect", "cy.contains"] +- } +- ], +- "import/no-extraneous-dependencies": [ +- "error", +- { +- "devDependencies": true, +- "optionalDependencies": true, +- "peerDependencies": true, +- "bundledDependencies": true +- } +- ] +- } +-} +diff --git a/packages/app/cypress/integration/app.js b/packages/app/cypress/integration/app.js +deleted file mode 100644 +index 43fb2e3..0000000 +--- a/packages/app/cypress/integration/app.js ++++ /dev/null +@@ -1,6 +0,0 @@ +-describe('App', () => { +- it('should render the catalog', () => { +- cy.visit('/'); +- cy.contains('My Company Catalog'); +- }); +-}); +diff --git a/packages/app/e2e-tests/app.test.ts b/packages/app/e2e-tests/app.test.ts +new file mode 100644 +index 0000000..d45bc0d +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,23 @@ ++/* ++ * Copyright 2020 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { test, expect } from '@playwright/test'; ++ ++test('App should render the welcome page', async ({ page }) => { ++ await page.goto('/'); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 3ed23fe..2100405 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,25 +5,38 @@ + "bundled": true, ++ "backstage": { ++ "role": "frontend" ++ }, ++ "scripts": { ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "clean": "backstage-cli package clean", ++ "test": "backstage-cli package test", ++ "lint": "backstage-cli package lint" ++ }, + "dependencies": { +- "@backstage/app-defaults": "^0.1.6", +- "@backstage/catalog-model": "^0.9.10", +- "@backstage/cli": "^0.13.1", +- "@backstage/core-app-api": "^0.5.2", +- "@backstage/core-components": "^0.8.7", +- "@backstage/core-plugin-api": "^0.6.0", +- "@backstage/integration-react": "^0.1.20", +- "@backstage/plugin-api-docs": "^0.7.1", +- "@backstage/plugin-catalog": "^0.7.11", +- "@backstage/plugin-catalog-common": "^0.1.2", +- "@backstage/plugin-catalog-graph": "^0.2.9", +- "@backstage/plugin-catalog-import": "^0.8.0", +- "@backstage/plugin-catalog-react": "^0.6.13", +- "@backstage/plugin-github-actions": "^0.4.34", +- "@backstage/plugin-org": "^0.4.1", +- "@backstage/plugin-permission-react": "^0.3.0", +- "@backstage/plugin-scaffolder": "^0.12.1", +- "@backstage/plugin-search": "^0.6.1", +- "@backstage/plugin-tech-radar": "^0.5.4", +- "@backstage/plugin-techdocs": "^0.13.2", +- "@backstage/plugin-user-settings": "^0.3.18", +- "@backstage/theme": "^0.2.14", ++ "@backstage/app-defaults": "^1.4.5-next.1", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/core-app-api": "^1.11.1-next.0", ++ "@backstage/core-components": "^0.13.8-next.1", ++ "@backstage/core-plugin-api": "^1.8.0-next.0", ++ "@backstage/integration-react": "^1.1.21-next.1", ++ "@backstage/plugin-api-docs": "^0.9.13-next.1", ++ "@backstage/plugin-catalog": "^1.15.0-next.1", ++ "@backstage/plugin-catalog-common": "^1.0.17", ++ "@backstage/plugin-catalog-graph": "^0.2.38-next.1", ++ "@backstage/plugin-catalog-import": "^0.10.2-next.1", ++ "@backstage/plugin-catalog-react": "^1.9.0-next.1", ++ "@backstage/plugin-github-actions": "^0.6.7-next.1", ++ "@backstage/plugin-org": "^0.6.16-next.1", ++ "@backstage/plugin-permission-react": "^0.4.17-next.0", ++ "@backstage/plugin-scaffolder": "^1.16.0-next.1", ++ "@backstage/plugin-search": "^1.4.2-next.1", ++ "@backstage/plugin-search-react": "^1.7.2-next.1", ++ "@backstage/plugin-tech-radar": "^0.6.10-next.1", ++ "@backstage/plugin-techdocs": "^1.8.1-next.1", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.2-next.1", ++ "@backstage/plugin-techdocs-react": "^1.1.13-next.1", ++ "@backstage/plugin-user-settings": "^0.7.12-next.1", ++ "@backstage/theme": "^0.4.4-next.0", + "@material-ui/core": "^4.12.2", +@@ -33,29 +46,15 @@ + "react-dom": "^17.0.2", +- "react-router": "6.0.0-beta.0", +- "react-router-dom": "6.0.0-beta.0", +- "react-use": "^15.3.3" ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^0.2.4", ++ "@backstage/test-utils": "^1.4.5-next.0", ++ "@playwright/test": "^1.32.3", + "@testing-library/jest-dom": "^5.10.1", +- "@testing-library/react": "^10.4.1", +- "@testing-library/user-event": "^12.0.7", +- "@types/jest": "^26.0.7", +- "@types/node": "^14.14.32", ++ "@testing-library/react": "^12.1.3", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^8.0.0", + "@types/react-dom": "*", +- "cross-env": "^7.0.0", +- "cypress": "^7.3.0", +- "eslint-plugin-cypress": "^2.10.3", +- "start-server-and-test": "^1.10.11" +- }, +- "scripts": { +- "start": "backstage-cli app:serve", +- "build": "backstage-cli app:build", +- "clean": "backstage-cli clean", +- "test": "backstage-cli test", +- "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", +- "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", +- "lint": "backstage-cli lint", +- "cy:dev": "cypress open", +- "cy:run": "cypress run" ++ "cross-env": "^7.0.0" + }, +diff --git a/packages/app/public/index.html b/packages/app/public/index.html +index a936c73..fc758ee 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -10,3 +10,2 @@ + /> +- + +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 9b65186..8d62f29 100644 +--- a/packages/app/src/App.tsx ++++ b/packages/app/src/App.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { Navigate, Route } from 'react-router'; ++import { Navigate, Route } from 'react-router-dom'; + import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; +@@ -17,3 +17,2 @@ import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -22,2 +21,4 @@ import { + } from '@backstage/plugin-techdocs'; ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; + import { UserSettingsPage } from '@backstage/plugin-user-settings'; +@@ -30,6 +31,6 @@ import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; + import { createApp } from '@backstage/app-defaults'; +-import { FlatRoutes } from '@backstage/core-app-api'; ++import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; + import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; +-import { PermissionedRoute } from '@backstage/plugin-permission-react'; +-import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common'; ++import { RequirePermission } from '@backstage/plugin-permission-react'; ++import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; + +@@ -41,2 +42,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -47,2 +49,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -54,8 +57,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -67,5 +67,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -80,6 +82,9 @@ const routes = ( + /> +- } ++ element={ ++ ++ ++ ++ } + /> +@@ -93,4 +98,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -100,5 +105,3 @@ const App = () => ( + +- ++ , + ); +- +-export default App; +diff --git a/packages/app/src/components/Root/LogoFull.tsx b/packages/app/src/components/Root/LogoFull.tsx +index c7b1c84..47e3b73 100644 +--- a/packages/app/src/components/Root/LogoFull.tsx ++++ b/packages/app/src/components/Root/LogoFull.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/LogoIcon.tsx b/packages/app/src/components/Root/LogoIcon.tsx +index 073cf6e..7eae8c7 100644 +--- a/packages/app/src/components/Root/LogoIcon.tsx ++++ b/packages/app/src/components/Root/LogoIcon.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx +index 7e98c7d..6768b48 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,19 +1,3 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- +-import React, { useContext, PropsWithChildren } from 'react'; +-import { Link, makeStyles } from '@material-ui/core'; ++import React, { PropsWithChildren } from 'react'; ++import { makeStyles } from '@material-ui/core'; + import HomeIcon from '@material-ui/icons/Home'; +@@ -25,3 +9,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -30,6 +13,3 @@ import { + } from '@backstage/plugin-user-settings'; +-import { +- SidebarSearchModal, +- SearchContextProvider, +-} from '@backstage/plugin-search'; ++import { SidebarSearchModal } from '@backstage/plugin-search'; + import { +@@ -37,3 +17,2 @@ import { + sidebarConfig, +- SidebarContext, + SidebarDivider, +@@ -44,2 +23,4 @@ import { + SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -65,3 +46,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -69,8 +50,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -86,5 +62,3 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + } to="/search"> +- +- +- {' '} ++ + +diff --git a/packages/app/src/components/Root/index.ts b/packages/app/src/components/Root/index.ts +index dff706f..3528881 100644 +--- a/packages/app/src/components/Root/index.ts ++++ b/packages/app/src/components/Root/index.ts +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + export { Root } from './Root'; +diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx +index 84d0944..6722ea2 100644 +--- a/packages/app/src/components/catalog/EntityPage.tsx ++++ b/packages/app/src/components/catalog/EntityPage.tsx +@@ -1,16 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ + import React from 'react'; +@@ -42,2 +27,4 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +@@ -70,2 +57,13 @@ import { + ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); ++ + const cicdContent = ( +@@ -107,2 +105,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -169,3 +175,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -196,3 +202,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -215,3 +221,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -315,3 +321,6 @@ const systemPage = ( + +- ++ ++ ++ ++ + +diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx +index a88e725..1788dde 100644 +--- a/packages/app/src/components/search/SearchPage.tsx ++++ b/packages/app/src/components/search/SearchPage.tsx +@@ -1,5 +1,5 @@ + import React from 'react'; +-import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; ++import { makeStyles, Theme, Grid, Paper } from '@material-ui/core'; + +-import { CatalogResultListItem } from '@backstage/plugin-catalog'; ++import { CatalogSearchResultListItem } from '@backstage/plugin-catalog'; + import { +@@ -8,4 +8,5 @@ import { + } from '@backstage/plugin-catalog-react'; +-import { DocsResultListItem } from '@backstage/plugin-techdocs'; ++import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; + ++import { SearchType } from '@backstage/plugin-search'; + import { +@@ -14,6 +15,5 @@ import { + SearchResult, +- SearchType, +- DefaultResultListItem, ++ SearchPagination, + useSearch, +-} from '@backstage/plugin-search'; ++} from '@backstage/plugin-search-react'; + import { +@@ -111,32 +111,6 @@ const SearchPage = () => { + ++ + +- {({ results }) => ( +- +- {results.map(({ type, document }) => { +- switch (type) { +- case 'software-catalog': +- return ( +- +- ); +- case 'techdocs': +- return ( +- +- ); +- default: +- return ( +- +- ); +- } +- })} +- +- )} ++ } /> ++ } /> + +diff --git a/packages/backend/.eslintrc.js b/packages/backend/.eslintrc.js +index 16a033d..e2a53a6 100644 +--- a/packages/backend/.eslintrc.js ++++ b/packages/backend/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint.backend')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index 31231a3..18548e9 100644 +--- a/packages/backend/Dockerfile ++++ b/packages/backend/Dockerfile +@@ -7,3 +7,3 @@ + # yarn tsc +-# yarn build ++# yarn build:backend + # +@@ -11,6 +11,29 @@ + +-FROM node:14-buster-slim ++FROM node:18-bookworm-slim + ++# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends python3 g++ build-essential && \ ++ yarn config set python /usr/bin/python3 ++ ++# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, ++# in which case you should also move better-sqlite3 to "devDependencies" in package.json. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends libsqlite3-dev ++ ++# From here on we use the least-privileged `node` user to run the backend. ++USER node ++ ++# This should create the app dir as `node`. ++# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. ++# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. + WORKDIR /app + ++# This switches many Node.js dependencies to production mode. ++ENV NODE_ENV production ++ + # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. +@@ -18,11 +41,12 @@ WORKDIR /app + # and along with yarn.lock and the root package.json, that's enough to run yarn install. +-COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ ++COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ + RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz + +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + + # Then copy the rest of the backend bundle, along with any other files we might want. +-COPY packages/backend/dist/bundle.tar.gz app-config.yaml ./ ++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"] +diff --git a/packages/backend/README.md b/packages/backend/README.md +index 02426ef..3607b0a 100644 +--- a/packages/backend/README.md ++++ b/packages/backend/README.md +@@ -18,4 +18,2 @@ To run the example backend, first go to the project root and run + yarn install +-yarn tsc +-yarn build + ``` +@@ -27,7 +25,2 @@ After that, go to the `packages/backend` directory and run + ```bash +-AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x \ +-AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x \ +-AUTH_OAUTH2_CLIENT_ID=x AUTH_OAUTH2_CLIENT_SECRET=x \ +-AUTH_OAUTH2_AUTH_URL=x AUTH_OAUTH2_TOKEN_URL=x \ +-LOG_LEVEL=debug \ + yarn start +@@ -35,4 +28,4 @@ yarn start + +-Substitute `x` for actual values, or leave them as dummy values just to try out +-the backend without using the auth or sentry features. ++If you want to override any configuration locally, for example adding any secrets, ++you can do so in `app-config.local.yaml`. + +@@ -45,3 +38,3 @@ locations to the backend. These are places where the backend can find some + entity descriptor data to consume and serve. For more information, see +-[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog). ++[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). + +@@ -65,2 +58,2 @@ and + - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +-- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) ++- [Backstage Documentation](https://backstage.io/docs) +diff --git a/packages/backend/package.json b/packages/backend/package.json +index 662347b..f224eaf 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,30 +6,33 @@ + "private": true, ++ "backstage": { ++ "role": "backend" ++ }, + "scripts": { +- "build": "backstage-cli backend:bundle", +- "build-image": "docker build ../.. -f Dockerfile --tag backstage", +- "start": "backstage-cli backend:dev", +- "lint": "backstage-cli lint", +- "test": "backstage-cli test", +- "clean": "backstage-cli clean", +- "migrate:create": "knex migrate:make -x ts" ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "lint": "backstage-cli package lint", ++ "test": "backstage-cli package test", ++ "clean": "backstage-cli package clean", ++ "build-image": "docker build ../.. -f Dockerfile --tag backstage" + }, + "dependencies": { +- "app": "file:../app", +- "@backstage/backend-common": "^0.10.6", +- "@backstage/backend-tasks": "^0.1.5", +- "@backstage/catalog-model": "^0.9.10", +- "@backstage/catalog-client": "^0.5.5", +- "@backstage/config": "^0.1.13", +- "@backstage/plugin-app-backend": "^0.3.23", +- "@backstage/plugin-auth-backend": "^0.9.0", +- "@backstage/plugin-catalog-backend": "^0.21.2", +- "@backstage/plugin-permission-common": "^0.4.0", +- "@backstage/plugin-permission-node": "^0.4.2", +- "@backstage/plugin-proxy-backend": "^0.2.17", +- "@backstage/plugin-scaffolder-backend": "^0.15.23", +- "@backstage/plugin-search-backend": "^0.4.1", +- "@backstage/plugin-search-backend-node": "^0.4.5", +- "@backstage/plugin-techdocs-backend": "^0.13.2", +- "@gitbeaker/node": "^34.6.0", +- "@octokit/rest": "^18.5.3", ++ "@backstage/backend-common": "^0.19.9-next.1", ++ "@backstage/backend-tasks": "^0.5.12-next.1", ++ "@backstage/catalog-client": "^1.4.5", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/config": "^1.1.1", ++ "@backstage/plugin-app-backend": "^0.3.55-next.1", ++ "@backstage/plugin-auth-backend": "^0.20.0-next.1", ++ "@backstage/plugin-auth-node": "^0.4.1-next.1", ++ "@backstage/plugin-catalog-backend": "^1.15.0-next.1", ++ "@backstage/plugin-permission-common": "^0.7.9", ++ "@backstage/plugin-permission-node": "^0.7.18-next.1", ++ "@backstage/plugin-proxy-backend": "^0.4.5-next.1", ++ "@backstage/plugin-scaffolder-backend": "^1.19.0-next.1", ++ "@backstage/plugin-search-backend": "^1.4.7-next.1", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.16-next.1", ++ "@backstage/plugin-search-backend-node": "^1.2.11-next.1", ++ "@backstage/plugin-techdocs-backend": "^1.9.0-next.1", ++ "app": "link:../app", ++ "better-sqlite3": "^8.0.0", + "dockerode": "^3.3.1", +@@ -37,4 +40,3 @@ + "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "pg": "^8.3.0", + "winston": "^3.2.1" +@@ -42,6 +44,7 @@ + "devDependencies": { +- "@backstage/cli": "^0.13.1", ++ "@backstage/cli": "^0.24.0-next.1", + "@types/dockerode": "^3.3.0", + "@types/express": "^4.17.6", +- "@types/express-serve-static-core": "^4.17.5" ++ "@types/express-serve-static-core": "^4.17.5", ++ "@types/luxon": "^2.0.4" + }, +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 70bc66b..04c4ff9 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -17,3 +17,3 @@ import { + DatabaseManager, +- SingleHostDiscovery, ++ HostDiscovery, + UrlReaders, +@@ -32,2 +32,3 @@ import { PluginEnvironment } from './types'; + import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; + +@@ -36,7 +37,11 @@ function makeCreateEnv(config: Config) { + const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++ const discovery = HostDiscovery.fromConfig(config); + const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++ const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); + const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); ++ const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); ++ ++ const identity = DefaultIdentityClient.create({ ++ discovery, ++ }); + const permissions = ServerPermissionClient.fromConfig(config, { +@@ -63,2 +68,3 @@ function makeCreateEnv(config: Config) { + permissions, ++ identity, + }; +@@ -106,3 +112,3 @@ module.hot?.accept(); + main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); ++ console.error('Backend failed to start up', error); + process.exit(1); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +index 14e19a1..7c37f68 100644 +--- a/packages/backend/src/plugins/app.ts ++++ b/packages/backend/src/plugins/app.ts +@@ -4,11 +4,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- database, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + return await createRouter({ +- logger, +- config, +- database, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, + appPackageName: 'app', +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +index 015c864..77eb6aa 100644 +--- a/packages/backend/src/plugins/auth.ts ++++ b/packages/backend/src/plugins/auth.ts +@@ -1,2 +1,6 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; ++import { ++ createRouter, ++ providers, ++ defaultAuthProviderFactories, ++} from '@backstage/plugin-auth-backend'; + import { Router } from 'express'; +@@ -4,15 +8,46 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +- tokenManager, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + return await createRouter({ +- logger, +- config, +- database, +- discovery, +- tokenManager, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, ++ providerFactories: { ++ ...defaultAuthProviderFactories, ++ ++ // This replaces the default GitHub auth provider with a customized one. ++ // The `signIn` option enables sign-in for this provider, using the ++ // identity resolution logic that's provided in the `resolver` callback. ++ // ++ // This particular resolver makes all users share a single "guest" identity. ++ // It should only be used for testing and trying out Backstage. ++ // ++ // If you want to use a production ready resolver you can switch to ++ // the one that is commented out below, it looks up a user entity in the ++ // catalog using the GitHub username of the authenticated user. ++ // That resolver requires you to have user entities populated in the catalog, ++ // for example using https://backstage.io/docs/integrations/github/org ++ // ++ // There are other resolvers to choose from, and you can also create ++ // your own, see the auth documentation for more details: ++ // ++ // https://backstage.io/docs/auth/identity-resolver ++ github: providers.github.create({ ++ signIn: { ++ resolver(_, ctx) { ++ const userRef = 'user:default/guest'; // Must be a full entity reference ++ return ctx.issueToken({ ++ claims: { ++ sub: userRef, // The user's own identity ++ ent: [userRef], // A list of identities that the user claims ownership through ++ }, ++ }); ++ }, ++ // resolver: providers.github.resolvers.usernameMatchingUserEntityName(), ++ }, ++ }), ++ }, + }); +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +index 506f6d9..54ec393 100644 +--- a/packages/backend/src/plugins/proxy.ts ++++ b/packages/backend/src/plugins/proxy.ts +@@ -4,8 +4,10 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ }); + } +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +index 6be2e97..a12fee2 100644 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ b/packages/backend/src/plugins/scaffolder.ts +@@ -1,5 +1,3 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; + import { CatalogClient } from '@backstage/catalog-client'; + import { createRouter } from '@backstage/plugin-scaffolder-backend'; +-import Docker from 'dockerode'; + import { Router } from 'express'; +@@ -7,20 +5,17 @@ import type { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- database, +- reader, +- discovery, +-}: PluginEnvironment): Promise { +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- const catalogClient = new CatalogClient({ discoveryApi: discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ const catalogClient = new CatalogClient({ ++ discoveryApi: env.discovery, ++ }); + + return await createRouter({ +- containerRunner, +- logger, +- config, +- database, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ reader: env.reader, + catalogClient, +- reader, ++ identity: env.identity, ++ permissions: env.permissions, + }); +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +index a0a1cc3..e9469dc 100644 +--- a/packages/backend/src/plugins/search.ts ++++ b/packages/backend/src/plugins/search.ts +@@ -7,15 +7,25 @@ import { + import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend'; ++import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend'; ++import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; ++import { Router } from 'express'; + +-export default async function createPlugin({ +- logger, +- permissions, +- discovery, +- config, +- tokenManager, +-}: PluginEnvironment) { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); ++ const searchEngine = new LunrSearchEngine({ ++ logger: env.logger, ++ }); ++ const indexBuilder = new IndexBuilder({ ++ logger: env.logger, ++ searchEngine, ++ }); ++ ++ const schedule = env.scheduler.createScheduledTaskRunner({ ++ frequency: { minutes: 10 }, ++ timeout: { minutes: 15 }, ++ // A 3 second delay gives the backend server a chance to initialize before ++ // any collators are executed, which may attempt requests against the API. ++ initialDelay: { seconds: 3 }, ++ }); + +@@ -24,6 +34,6 @@ export default async function createPlugin({ + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { +- discovery, +- tokenManager, ++ schedule, ++ factory: DefaultCatalogCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, + }), +@@ -33,7 +43,7 @@ export default async function createPlugin({ + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultTechDocsCollator.fromConfig(config, { +- discovery, +- logger, +- tokenManager, ++ schedule, ++ factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ logger: env.logger, ++ tokenManager: env.tokenManager, + }), +@@ -44,6 +54,4 @@ export default async function createPlugin({ + const { scheduler } = await indexBuilder.build(); ++ scheduler.start(); + +- // A 3 second delay gives the backend server a chance to initialize before +- // any collators are executed, which may attempt requests against the API. +- setTimeout(() => scheduler.start(), 3000); + useHotCleanup(module, () => scheduler.stop()); +@@ -53,5 +61,5 @@ export default async function createPlugin({ + types: indexBuilder.getDocumentTypes(), +- permissions, +- config, +- logger, ++ permissions: env.permissions, ++ config: env.config, ++ logger: env.logger, + }); +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +index 054c64d..be8bb0c 100644 +--- a/packages/backend/src/plugins/techdocs.ts ++++ b/packages/backend/src/plugins/techdocs.ts +@@ -11,13 +11,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +- cache, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, ++ const preparers = await Preparers.fromConfig(env.config, { ++ logger: env.logger, ++ reader: env.reader, + }); +@@ -29,4 +25,4 @@ export default async function createPlugin({ + // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, ++ const generators = await Generators.fromConfig(env.config, { ++ logger: env.logger, + containerRunner, +@@ -37,5 +33,5 @@ export default async function createPlugin({ + // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, ++ const publisher = await Publisher.fromConfig(env.config, { ++ logger: env.logger, ++ discovery: env.discovery, + }); +@@ -49,6 +45,6 @@ export default async function createPlugin({ + publisher, +- logger, +- config, +- discovery, +- cache, ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ cache: env.cache, + }); +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +index 0862b0e..9cd2c74 100644 +--- a/packages/backend/src/types.ts ++++ b/packages/backend/src/types.ts +@@ -10,3 +10,4 @@ import { + import { PluginTaskScheduler } from '@backstage/backend-tasks'; +-import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; ++import { PermissionEvaluator } from '@backstage/plugin-permission-common'; ++import { IdentityApi } from '@backstage/plugin-auth-node'; + +@@ -21,3 +22,4 @@ export type PluginEnvironment = { + scheduler: PluginTaskScheduler; +- permissions: PermissionAuthorizer; ++ permissions: PermissionEvaluator; ++ identity: IdentityApi; + }; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..37c7fb1 +--- /dev/null ++++ b/playwright.config.ts +@@ -0,0 +1,60 @@ ++/* ++ * Copyright 2023 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { defineConfig } from '@playwright/test'; ++import { generateProjects } from '@backstage/e2e-test-utils/playwright'; ++ ++/** ++ * See https://playwright.dev/docs/test-configuration. ++ */ ++export default defineConfig({ ++ timeout: 60_000, ++ ++ expect: { ++ timeout: 5_000, ++ }, ++ ++ // Run your local dev server before starting the tests ++ webServer: process.env.CI ++ ? [] ++ : [ ++ { ++ command: 'yarn start', ++ port: 3000, ++ reuseExistingServer: true, ++ timeout: 60_000, ++ }, ++ ], ++ ++ forbidOnly: !!process.env.CI, ++ ++ retries: process.env.CI ? 2 : 0, ++ ++ reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]], ++ ++ use: { ++ actionTimeout: 0, ++ baseURL: ++ process.env.PLAYWRIGHT_URL ?? ++ (process.env.CI ? 'http://localhost:7007' : 'http://localhost:3000'), ++ screenshot: 'only-on-failure', ++ trace: 'on-first-retry', ++ }, ++ ++ outputDir: 'node_modules/.cache/e2e-test-results', ++ ++ projects: generateProjects(), // Find all packages with e2e-test folders ++}); +diff --git a/plugins/README.md b/plugins/README.md +new file mode 100644 +index 0000000..d7865fd +--- /dev/null ++++ b/plugins/README.md +@@ -0,0 +1,9 @@ ++# The Plugins Folder ++ ++This is where your own plugins and their associated modules live, each in a ++separate folder of its own. ++ ++If you want to create a new plugin here, go to your project root directory, run ++the command `yarn new`, and follow the on-screen instructions. ++ ++You can also check out existing plugins on [the plugin marketplace](https://backstage.io/plugins)! +diff --git a/yarn.lock b/yarn.lock +new file mode 100644 +index 0000000..5ad7fe2 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1 @@ ++# intentionally left empty diff --git a/diffs/0.4.19..0.5.7-next.1.diff b/diffs/0.4.19..0.5.7-next.1.diff new file mode 100644 index 00000000..8d63fd37 --- /dev/null +++ b/diffs/0.4.19..0.5.7-next.1.diff @@ -0,0 +1,1674 @@ +diff --git a/.dockerignore b/.dockerignore +index 63c9c34..05edb62 100644 +--- a/.dockerignore ++++ b/.dockerignore +@@ -1,5 +1,8 @@ + .git ++.yarn/cache ++.yarn/install-state.gz + node_modules +-packages +-!packages/backend/dist ++packages/*/src ++packages/*/node_modules + plugins ++*.local.yaml +diff --git a/.eslintignore b/.eslintignore +new file mode 100644 +index 0000000..e5b1994 +--- /dev/null ++++ b/.eslintignore +@@ -0,0 +1 @@ ++playwright.config.ts +diff --git a/.gitignore b/.gitignore +index d16a8d3..fbf8139 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -17,2 +17,11 @@ node_modules/ + ++# Yarn 3 files ++.pnp.* ++.yarn/* ++!.yarn/patches ++!.yarn/plugins ++!.yarn/releases ++!.yarn/sdks ++!.yarn/versions ++ + # Node version directives +@@ -39 +48,7 @@ site + *-credentials.yaml ++ ++# vscode database functionality support files ++*.session.sql ++ ++# E2E test reports ++e2e-test-report/ +diff --git a/app-config.production.yaml b/app-config.production.yaml +index 5e36c23..8f0751c 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -1,3 +1,3 @@ + app: +- # Should be the same as backend.baseUrl when using the `app-backend` plugin ++ # Should be the same as backend.baseUrl when using the `app-backend` plugin. + baseUrl: http://localhost:7007 +@@ -5,4 +5,31 @@ app: + backend: ++ # Note that the baseUrl should be the URL that the browser and other clients ++ # should use when communicating with the backend, i.e. it needs to be ++ # reachable not just from within the backend host, but from all of your ++ # callers. When its value is "http://localhost:7007", it's strictly private ++ # and can't be reached by others. + baseUrl: http://localhost:7007 +- listen: +- port: 7007 ++ # The listener can also be expressed as a single : string. In this case we bind to ++ # all interfaces, the most permissive setting. The right value depends on your specific deployment. ++ listen: ':7007' ++ ++ # config options: https://node-postgres.com/api/client ++ database: ++ client: pg ++ connection: ++ host: ${POSTGRES_HOST} ++ port: ${POSTGRES_PORT} ++ user: ${POSTGRES_USER} ++ password: ${POSTGRES_PASSWORD} ++ # https://node-postgres.com/features/ssl ++ # you can set the sslmode configuration option via the `PGSSLMODE` environment variable ++ # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require) ++ # ssl: ++ # ca: # if you have a CA file and want to verify it you can uncomment this section ++ # $file: /ca/server.crt ++ ++catalog: ++ # Overrides the default list locations from app-config.yaml as these contain example data. ++ # See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details ++ # on how to get entities into the catalog. ++ locations: [] +diff --git a/app-config.yaml b/app-config.yaml +index d45e354..08d2abf 100644 +--- a/app-config.yaml ++++ b/app-config.yaml +@@ -9,3 +9,3 @@ backend: + # Used for enabling authentication, secret is shared by all backend plugins +- # See https://backstage.io/docs/tutorials/backend-to-backend-auth for ++ # See https://backstage.io/docs/auth/service-to-service-auth for + # information on the format +@@ -17,2 +17,4 @@ backend: + port: 7007 ++ # Uncomment the following host directive to bind to specific interfaces ++ # host: 127.0.0.1 + csp: +@@ -23,9 +25,9 @@ backend: + origin: http://localhost:3000 +- methods: [GET, POST, PUT, DELETE] ++ methods: [GET, HEAD, PATCH, POST, PUT, DELETE] + credentials: true ++ # This is for local development only, it is not recommended to use this in production ++ # The production database configuration is stored in app-config.production.yaml + database: +- client: sqlite3 ++ client: better-sqlite3 + connection: ':memory:' +- cache: +- store: memory + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir +@@ -35,2 +37,4 @@ integrations: + - host: github.com ++ # This is a Personal Access Token or PAT from GitHub. You can find out how to generate this token, and more information ++ # about setting up the GitHub integration here: https://backstage.io/docs/getting-started/configuration#setting-up-a-github-integration + token: ${GITHUB_TOKEN} +@@ -42,5 +46,8 @@ integrations: + proxy: +- '/test': +- target: 'https://example.com' +- changeOrigin: true ++ ### Example for how to add a proxy endpoint for the frontend. ++ ### A typical reason to do this is to handle HTTPS and CORS for internal services. ++ # endpoints: ++ # '/test': ++ # target: 'https://example.com' ++ # changeOrigin: true + +@@ -69,40 +76,28 @@ catalog: + rules: +- - allow: [Component, System, API, Group, User, Resource, Location] ++ - allow: [Component, System, API, Resource, Location] + locations: +- # Backstage example components +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-components.yaml ++ # Local example data, file locations are relative to the backend process, typically `packages/backend` ++ - type: file ++ target: ../../examples/entities.yaml + +- # Backstage example systems +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-systems.yaml +- +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml +- +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml +- +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.yaml +- +- # Backstage example templates +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/react-ssr-template/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/springboot-grpc-template/template.yaml ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml + rules: + - allow: [Template] +- - type: url +- target: https://github.com/spotify/cookiecutter-golang/blob/master/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/docs-template/template.yaml ++ ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml + rules: +- - allow: [Template] ++ - allow: [User, Group] ++ ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml ++ ++ ## Uncomment these lines to add an example org ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme-corp.yaml ++ # rules: ++ # - allow: [User, Group] +diff --git a/backstage.json b/backstage.json +index 5d45824..707af29 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "0.4.19" ++ "version": "1.20.0-next.1" + } +diff --git a/examples/entities.yaml b/examples/entities.yaml +new file mode 100644 +index 0000000..447e8b1 +--- /dev/null ++++ b/examples/entities.yaml +@@ -0,0 +1,41 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system ++apiVersion: backstage.io/v1alpha1 ++kind: System ++metadata: ++ name: examples ++spec: ++ owner: guests ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: example-website ++spec: ++ type: website ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ providesApis: [example-grpc-api] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api ++apiVersion: backstage.io/v1alpha1 ++kind: API ++metadata: ++ name: example-grpc-api ++spec: ++ type: grpc ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ definition: | ++ syntax = "proto3"; ++ ++ service Exampler { ++ rpc Example (ExampleMessage) returns (ExampleMessage) {}; ++ } ++ ++ message ExampleMessage { ++ string example = 1; ++ }; +diff --git a/examples/org.yaml b/examples/org.yaml +new file mode 100644 +index 0000000..a10e81f +--- /dev/null ++++ b/examples/org.yaml +@@ -0,0 +1,17 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user ++apiVersion: backstage.io/v1alpha1 ++kind: User ++metadata: ++ name: guest ++spec: ++ memberOf: [guests] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group ++apiVersion: backstage.io/v1alpha1 ++kind: Group ++metadata: ++ name: guests ++spec: ++ type: team ++ children: [] +diff --git a/examples/template/content/catalog-info.yaml b/examples/template/content/catalog-info.yaml +new file mode 100644 +index 0000000..d4ccca4 +--- /dev/null ++++ b/examples/template/content/catalog-info.yaml +@@ -0,0 +1,8 @@ ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: ${{ values.name | dump }} ++spec: ++ type: service ++ owner: user:guest ++ lifecycle: experimental +diff --git a/examples/template/content/index.js b/examples/template/content/index.js +new file mode 100644 +index 0000000..071ce5a +--- /dev/null ++++ b/examples/template/content/index.js +@@ -0,0 +1 @@ ++console.log('Hello from ${{ values.name }}!'); +diff --git a/examples/template/content/package.json b/examples/template/content/package.json +new file mode 100644 +index 0000000..86f968a +--- /dev/null ++++ b/examples/template/content/package.json +@@ -0,0 +1,5 @@ ++{ ++ "name": "${{ values.name }}", ++ "private": true, ++ "dependencies": {} ++} +diff --git a/examples/template/template.yaml b/examples/template/template.yaml +new file mode 100644 +index 0000000..33f262b +--- /dev/null ++++ b/examples/template/template.yaml +@@ -0,0 +1,74 @@ ++apiVersion: scaffolder.backstage.io/v1beta3 ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template ++kind: Template ++metadata: ++ name: example-nodejs-template ++ title: Example Node.js Template ++ description: An example template for the scaffolder that creates a simple Node.js service ++spec: ++ owner: user:guest ++ type: service ++ ++ # These parameters are used to generate the input form in the frontend, and are ++ # used to gather input data for the execution of the template. ++ parameters: ++ - title: Fill in some steps ++ required: ++ - name ++ properties: ++ name: ++ title: Name ++ type: string ++ description: Unique name of the component ++ ui:autofocus: true ++ ui:options: ++ rows: 5 ++ - title: Choose a location ++ required: ++ - repoUrl ++ properties: ++ repoUrl: ++ title: Repository Location ++ type: string ++ ui:field: RepoUrlPicker ++ ui:options: ++ allowedHosts: ++ - github.com ++ ++ # These steps are executed in the scaffolder backend, using data that we gathered ++ # via the parameters above. ++ steps: ++ # Each step executes an action, in this case one templates files into the working directory. ++ - id: fetch-base ++ name: Fetch Base ++ action: fetch:template ++ input: ++ url: ./content ++ values: ++ name: ${{ parameters.name }} ++ ++ # This step publishes the contents of the working directory to GitHub. ++ - id: publish ++ name: Publish ++ action: publish:github ++ input: ++ allowedHosts: ['github.com'] ++ description: This is ${{ parameters.name }} ++ repoUrl: ${{ parameters.repoUrl }} ++ ++ # The final step is to register our new component in the catalog. ++ - id: register ++ name: Register ++ action: catalog:register ++ input: ++ repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} ++ catalogInfoPath: '/catalog-info.yaml' ++ ++ # Outputs are displayed to the user after a successful execution of the template. ++ output: ++ links: ++ - title: Repository ++ url: ${{ steps['publish'].output.remoteUrl }} ++ - title: Open in catalog ++ icon: catalog ++ entityRef: ${{ steps['register'].output.entityRef }} +diff --git a/lerna.json b/lerna.json +index 322929d..529a62f 100644 +--- a/lerna.json ++++ b/lerna.json +@@ -3,4 +3,4 @@ + "npmClient": "yarn", +- "useWorkspaces": true, +- "version": "0.1.0" ++ "version": "0.1.0", ++ "$schema": "node_modules/lerna/schemas/lerna-schema.json" + } +diff --git a/package.json b/package.json +index 6f04cc3..2364da8 100644 +--- a/package.json ++++ b/package.json +@@ -5,3 +5,3 @@ + "engines": { +- "node": "14 || 16" ++ "node": "18 || 20" + }, +@@ -11,3 +11,4 @@ + "start-backend": "yarn workspace backend start", +- "build": "lerna run build", ++ "build:backend": "yarn workspace backend build", ++ "build:all": "backstage-cli repo build --all", + "build-image": "yarn workspace backend build-image", +@@ -15,11 +16,11 @@ + "tsc:full": "tsc --skipLibCheck false --incremental false", +- "clean": "backstage-cli clean && lerna run clean", +- "diff": "lerna run diff --", +- "test": "backstage-cli test", +- "test:all": "lerna run test -- --coverage", +- "lint": "lerna run lint --since origin/master --", +- "lint:all": "lerna run lint --", ++ "clean": "backstage-cli repo clean", ++ "test": "backstage-cli repo test", ++ "test:all": "backstage-cli repo test --coverage", ++ "test:e2e": "playwright test", ++ "fix": "backstage-cli repo fix", ++ "lint": "backstage-cli repo lint --since origin/master", ++ "lint:all": "backstage-cli repo lint", + "prettier:check": "prettier --check .", +- "create-plugin": "backstage-cli create-plugin --scope internal", +- "remove-plugin": "backstage-cli remove-plugin" ++ "new": "backstage-cli new --scope internal" + }, +@@ -32,8 +33,15 @@ + "devDependencies": { +- "@backstage/cli": "^0.13.2", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/e2e-test-utils": "^0.1.0", ++ "@playwright/test": "^1.32.3", + "@spotify/prettier-config": "^12.0.0", +- "concurrently": "^6.0.0", +- "lerna": "^4.0.0", ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^9.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.5.4" ++ "typescript": "~5.2.0" ++ }, ++ "resolutions": { ++ "@types/react": "^17", ++ "@types/react-dom": "^17" + }, +diff --git a/packages/README.md b/packages/README.md +new file mode 100644 +index 0000000..6327fa0 +--- /dev/null ++++ b/packages/README.md +@@ -0,0 +1,9 @@ ++# The Packages Folder ++ ++This is where your own applications and centrally managed libraries live, each ++in a separate folder of its own. ++ ++From the start there's an `app` folder (for the frontend) and a `backend` folder ++(for the Node backend), but you can also add more modules in here that house ++your core additions and adaptations, such as themes, common React component ++libraries, utilities, and similar. +diff --git a/packages/app/.eslintignore b/packages/app/.eslintignore +new file mode 100644 +index 0000000..a48cf0d +--- /dev/null ++++ b/packages/app/.eslintignore +@@ -0,0 +1 @@ ++public +diff --git a/packages/app/.eslintrc.js b/packages/app/.eslintrc.js +index 13573ef..e2a53a6 100644 +--- a/packages/app/.eslintrc.js ++++ b/packages/app/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/app/cypress.json b/packages/app/cypress.json +deleted file mode 100644 +index 5de7ebf..0000000 +--- a/packages/app/cypress.json ++++ /dev/null +@@ -1,5 +0,0 @@ +-{ +- "baseUrl": "http://localhost:3001", +- "fixturesFolder": false, +- "pluginsFile": false +-} +diff --git a/packages/app/cypress/.eslintrc.json b/packages/app/cypress/.eslintrc.json +deleted file mode 100644 +index 2b3a458..0000000 +--- a/packages/app/cypress/.eslintrc.json ++++ /dev/null +@@ -1,21 +0,0 @@ +-{ +- "plugins": ["cypress"], +- "extends": ["plugin:cypress/recommended"], +- "rules": { +- "jest/expect-expect": [ +- "error", +- { +- "assertFunctionNames": ["expect", "cy.contains"] +- } +- ], +- "import/no-extraneous-dependencies": [ +- "error", +- { +- "devDependencies": true, +- "optionalDependencies": true, +- "peerDependencies": true, +- "bundledDependencies": true +- } +- ] +- } +-} +diff --git a/packages/app/cypress/integration/app.js b/packages/app/cypress/integration/app.js +deleted file mode 100644 +index 43fb2e3..0000000 +--- a/packages/app/cypress/integration/app.js ++++ /dev/null +@@ -1,6 +0,0 @@ +-describe('App', () => { +- it('should render the catalog', () => { +- cy.visit('/'); +- cy.contains('My Company Catalog'); +- }); +-}); +diff --git a/packages/app/e2e-tests/app.test.ts b/packages/app/e2e-tests/app.test.ts +new file mode 100644 +index 0000000..d45bc0d +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,23 @@ ++/* ++ * Copyright 2020 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { test, expect } from '@playwright/test'; ++ ++test('App should render the welcome page', async ({ page }) => { ++ await page.goto('/'); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 447761d..2100405 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,25 +5,38 @@ + "bundled": true, ++ "backstage": { ++ "role": "frontend" ++ }, ++ "scripts": { ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "clean": "backstage-cli package clean", ++ "test": "backstage-cli package test", ++ "lint": "backstage-cli package lint" ++ }, + "dependencies": { +- "@backstage/app-defaults": "^0.1.7", +- "@backstage/catalog-model": "^0.9.10", +- "@backstage/cli": "^0.13.2", +- "@backstage/core-app-api": "^0.5.2", +- "@backstage/core-components": "^0.8.8", +- "@backstage/core-plugin-api": "^0.6.0", +- "@backstage/integration-react": "^0.1.21", +- "@backstage/plugin-api-docs": "^0.7.2", +- "@backstage/plugin-catalog": "^0.7.12", +- "@backstage/plugin-catalog-common": "^0.1.2", +- "@backstage/plugin-catalog-graph": "^0.2.10", +- "@backstage/plugin-catalog-import": "^0.8.1", +- "@backstage/plugin-catalog-react": "^0.6.14", +- "@backstage/plugin-github-actions": "^0.4.35", +- "@backstage/plugin-org": "^0.4.2-next.0", +- "@backstage/plugin-permission-react": "^0.3.0", +- "@backstage/plugin-scaffolder": "^0.12.2", +- "@backstage/plugin-search": "^0.6.2", +- "@backstage/plugin-tech-radar": "^0.5.5", +- "@backstage/plugin-techdocs": "^0.13.3", +- "@backstage/plugin-user-settings": "^0.3.19", +- "@backstage/theme": "^0.2.14", ++ "@backstage/app-defaults": "^1.4.5-next.1", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/core-app-api": "^1.11.1-next.0", ++ "@backstage/core-components": "^0.13.8-next.1", ++ "@backstage/core-plugin-api": "^1.8.0-next.0", ++ "@backstage/integration-react": "^1.1.21-next.1", ++ "@backstage/plugin-api-docs": "^0.9.13-next.1", ++ "@backstage/plugin-catalog": "^1.15.0-next.1", ++ "@backstage/plugin-catalog-common": "^1.0.17", ++ "@backstage/plugin-catalog-graph": "^0.2.38-next.1", ++ "@backstage/plugin-catalog-import": "^0.10.2-next.1", ++ "@backstage/plugin-catalog-react": "^1.9.0-next.1", ++ "@backstage/plugin-github-actions": "^0.6.7-next.1", ++ "@backstage/plugin-org": "^0.6.16-next.1", ++ "@backstage/plugin-permission-react": "^0.4.17-next.0", ++ "@backstage/plugin-scaffolder": "^1.16.0-next.1", ++ "@backstage/plugin-search": "^1.4.2-next.1", ++ "@backstage/plugin-search-react": "^1.7.2-next.1", ++ "@backstage/plugin-tech-radar": "^0.6.10-next.1", ++ "@backstage/plugin-techdocs": "^1.8.1-next.1", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.2-next.1", ++ "@backstage/plugin-techdocs-react": "^1.1.13-next.1", ++ "@backstage/plugin-user-settings": "^0.7.12-next.1", ++ "@backstage/theme": "^0.4.4-next.0", + "@material-ui/core": "^4.12.2", +@@ -33,29 +46,15 @@ + "react-dom": "^17.0.2", +- "react-router": "6.0.0-beta.0", +- "react-router-dom": "6.0.0-beta.0", +- "react-use": "^15.3.3" ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^0.2.4", ++ "@backstage/test-utils": "^1.4.5-next.0", ++ "@playwright/test": "^1.32.3", + "@testing-library/jest-dom": "^5.10.1", +- "@testing-library/react": "^10.4.1", +- "@testing-library/user-event": "^12.0.7", +- "@types/jest": "^26.0.7", +- "@types/node": "^14.14.32", ++ "@testing-library/react": "^12.1.3", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^8.0.0", + "@types/react-dom": "*", +- "cross-env": "^7.0.0", +- "cypress": "^7.3.0", +- "eslint-plugin-cypress": "^2.10.3", +- "start-server-and-test": "^1.10.11" +- }, +- "scripts": { +- "start": "backstage-cli app:serve", +- "build": "backstage-cli app:build", +- "clean": "backstage-cli clean", +- "test": "backstage-cli test", +- "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", +- "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", +- "lint": "backstage-cli lint", +- "cy:dev": "cypress open", +- "cy:run": "cypress run" ++ "cross-env": "^7.0.0" + }, +diff --git a/packages/app/public/index.html b/packages/app/public/index.html +index a936c73..fc758ee 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -10,3 +10,2 @@ + /> +- + +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 9b65186..8d62f29 100644 +--- a/packages/app/src/App.tsx ++++ b/packages/app/src/App.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { Navigate, Route } from 'react-router'; ++import { Navigate, Route } from 'react-router-dom'; + import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; +@@ -17,3 +17,2 @@ import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -22,2 +21,4 @@ import { + } from '@backstage/plugin-techdocs'; ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; + import { UserSettingsPage } from '@backstage/plugin-user-settings'; +@@ -30,6 +31,6 @@ import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; + import { createApp } from '@backstage/app-defaults'; +-import { FlatRoutes } from '@backstage/core-app-api'; ++import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; + import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; +-import { PermissionedRoute } from '@backstage/plugin-permission-react'; +-import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common'; ++import { RequirePermission } from '@backstage/plugin-permission-react'; ++import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; + +@@ -41,2 +42,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -47,2 +49,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -54,8 +57,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -67,5 +67,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -80,6 +82,9 @@ const routes = ( + /> +- } ++ element={ ++ ++ ++ ++ } + /> +@@ -93,4 +98,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -100,5 +105,3 @@ const App = () => ( + +- ++ , + ); +- +-export default App; +diff --git a/packages/app/src/components/Root/LogoFull.tsx b/packages/app/src/components/Root/LogoFull.tsx +index c7b1c84..47e3b73 100644 +--- a/packages/app/src/components/Root/LogoFull.tsx ++++ b/packages/app/src/components/Root/LogoFull.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/LogoIcon.tsx b/packages/app/src/components/Root/LogoIcon.tsx +index 073cf6e..7eae8c7 100644 +--- a/packages/app/src/components/Root/LogoIcon.tsx ++++ b/packages/app/src/components/Root/LogoIcon.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx +index 7e98c7d..6768b48 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,19 +1,3 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- +-import React, { useContext, PropsWithChildren } from 'react'; +-import { Link, makeStyles } from '@material-ui/core'; ++import React, { PropsWithChildren } from 'react'; ++import { makeStyles } from '@material-ui/core'; + import HomeIcon from '@material-ui/icons/Home'; +@@ -25,3 +9,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -30,6 +13,3 @@ import { + } from '@backstage/plugin-user-settings'; +-import { +- SidebarSearchModal, +- SearchContextProvider, +-} from '@backstage/plugin-search'; ++import { SidebarSearchModal } from '@backstage/plugin-search'; + import { +@@ -37,3 +17,2 @@ import { + sidebarConfig, +- SidebarContext, + SidebarDivider, +@@ -44,2 +23,4 @@ import { + SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -65,3 +46,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -69,8 +50,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -86,5 +62,3 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + } to="/search"> +- +- +- {' '} ++ + +diff --git a/packages/app/src/components/Root/index.ts b/packages/app/src/components/Root/index.ts +index dff706f..3528881 100644 +--- a/packages/app/src/components/Root/index.ts ++++ b/packages/app/src/components/Root/index.ts +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + export { Root } from './Root'; +diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx +index 84d0944..6722ea2 100644 +--- a/packages/app/src/components/catalog/EntityPage.tsx ++++ b/packages/app/src/components/catalog/EntityPage.tsx +@@ -1,16 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ + import React from 'react'; +@@ -42,2 +27,4 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +@@ -70,2 +57,13 @@ import { + ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); ++ + const cicdContent = ( +@@ -107,2 +105,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -169,3 +175,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -196,3 +202,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -215,3 +221,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -315,3 +321,6 @@ const systemPage = ( + +- ++ ++ ++ ++ + +diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx +index a88e725..1788dde 100644 +--- a/packages/app/src/components/search/SearchPage.tsx ++++ b/packages/app/src/components/search/SearchPage.tsx +@@ -1,5 +1,5 @@ + import React from 'react'; +-import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; ++import { makeStyles, Theme, Grid, Paper } from '@material-ui/core'; + +-import { CatalogResultListItem } from '@backstage/plugin-catalog'; ++import { CatalogSearchResultListItem } from '@backstage/plugin-catalog'; + import { +@@ -8,4 +8,5 @@ import { + } from '@backstage/plugin-catalog-react'; +-import { DocsResultListItem } from '@backstage/plugin-techdocs'; ++import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; + ++import { SearchType } from '@backstage/plugin-search'; + import { +@@ -14,6 +15,5 @@ import { + SearchResult, +- SearchType, +- DefaultResultListItem, ++ SearchPagination, + useSearch, +-} from '@backstage/plugin-search'; ++} from '@backstage/plugin-search-react'; + import { +@@ -111,32 +111,6 @@ const SearchPage = () => { + ++ + +- {({ results }) => ( +- +- {results.map(({ type, document }) => { +- switch (type) { +- case 'software-catalog': +- return ( +- +- ); +- case 'techdocs': +- return ( +- +- ); +- default: +- return ( +- +- ); +- } +- })} +- +- )} ++ } /> ++ } /> + +diff --git a/packages/backend/.eslintrc.js b/packages/backend/.eslintrc.js +index 16a033d..e2a53a6 100644 +--- a/packages/backend/.eslintrc.js ++++ b/packages/backend/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint.backend')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index 31231a3..18548e9 100644 +--- a/packages/backend/Dockerfile ++++ b/packages/backend/Dockerfile +@@ -7,3 +7,3 @@ + # yarn tsc +-# yarn build ++# yarn build:backend + # +@@ -11,6 +11,29 @@ + +-FROM node:14-buster-slim ++FROM node:18-bookworm-slim + ++# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends python3 g++ build-essential && \ ++ yarn config set python /usr/bin/python3 ++ ++# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, ++# in which case you should also move better-sqlite3 to "devDependencies" in package.json. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends libsqlite3-dev ++ ++# From here on we use the least-privileged `node` user to run the backend. ++USER node ++ ++# This should create the app dir as `node`. ++# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. ++# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. + WORKDIR /app + ++# This switches many Node.js dependencies to production mode. ++ENV NODE_ENV production ++ + # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. +@@ -18,11 +41,12 @@ WORKDIR /app + # and along with yarn.lock and the root package.json, that's enough to run yarn install. +-COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ ++COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ + RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz + +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + + # Then copy the rest of the backend bundle, along with any other files we might want. +-COPY packages/backend/dist/bundle.tar.gz app-config.yaml ./ ++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"] +diff --git a/packages/backend/README.md b/packages/backend/README.md +index 02426ef..3607b0a 100644 +--- a/packages/backend/README.md ++++ b/packages/backend/README.md +@@ -18,4 +18,2 @@ To run the example backend, first go to the project root and run + yarn install +-yarn tsc +-yarn build + ``` +@@ -27,7 +25,2 @@ After that, go to the `packages/backend` directory and run + ```bash +-AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x \ +-AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x \ +-AUTH_OAUTH2_CLIENT_ID=x AUTH_OAUTH2_CLIENT_SECRET=x \ +-AUTH_OAUTH2_AUTH_URL=x AUTH_OAUTH2_TOKEN_URL=x \ +-LOG_LEVEL=debug \ + yarn start +@@ -35,4 +28,4 @@ yarn start + +-Substitute `x` for actual values, or leave them as dummy values just to try out +-the backend without using the auth or sentry features. ++If you want to override any configuration locally, for example adding any secrets, ++you can do so in `app-config.local.yaml`. + +@@ -45,3 +38,3 @@ locations to the backend. These are places where the backend can find some + entity descriptor data to consume and serve. For more information, see +-[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog). ++[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). + +@@ -65,2 +58,2 @@ and + - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +-- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) ++- [Backstage Documentation](https://backstage.io/docs) +diff --git a/packages/backend/package.json b/packages/backend/package.json +index 72f911d..f224eaf 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,30 +6,33 @@ + "private": true, ++ "backstage": { ++ "role": "backend" ++ }, + "scripts": { +- "build": "backstage-cli backend:bundle", +- "build-image": "docker build ../.. -f Dockerfile --tag backstage", +- "start": "backstage-cli backend:dev", +- "lint": "backstage-cli lint", +- "test": "backstage-cli test", +- "clean": "backstage-cli clean", +- "migrate:create": "knex migrate:make -x ts" ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "lint": "backstage-cli package lint", ++ "test": "backstage-cli package test", ++ "clean": "backstage-cli package clean", ++ "build-image": "docker build ../.. -f Dockerfile --tag backstage" + }, + "dependencies": { ++ "@backstage/backend-common": "^0.19.9-next.1", ++ "@backstage/backend-tasks": "^0.5.12-next.1", ++ "@backstage/catalog-client": "^1.4.5", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/config": "^1.1.1", ++ "@backstage/plugin-app-backend": "^0.3.55-next.1", ++ "@backstage/plugin-auth-backend": "^0.20.0-next.1", ++ "@backstage/plugin-auth-node": "^0.4.1-next.1", ++ "@backstage/plugin-catalog-backend": "^1.15.0-next.1", ++ "@backstage/plugin-permission-common": "^0.7.9", ++ "@backstage/plugin-permission-node": "^0.7.18-next.1", ++ "@backstage/plugin-proxy-backend": "^0.4.5-next.1", ++ "@backstage/plugin-scaffolder-backend": "^1.19.0-next.1", ++ "@backstage/plugin-search-backend": "^1.4.7-next.1", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.16-next.1", ++ "@backstage/plugin-search-backend-node": "^1.2.11-next.1", ++ "@backstage/plugin-techdocs-backend": "^1.9.0-next.1", + "app": "link:../app", +- "@backstage/backend-common": "^0.10.7", +- "@backstage/backend-tasks": "^0.1.6", +- "@backstage/catalog-model": "^0.9.10", +- "@backstage/catalog-client": "^0.6.0", +- "@backstage/config": "^0.1.13", +- "@backstage/plugin-app-backend": "^0.3.24", +- "@backstage/plugin-auth-backend": "^0.10.0", +- "@backstage/plugin-catalog-backend": "^0.21.3", +- "@backstage/plugin-permission-common": "^0.4.0", +- "@backstage/plugin-permission-node": "^0.4.3", +- "@backstage/plugin-proxy-backend": "^0.2.18", +- "@backstage/plugin-scaffolder-backend": "^0.15.24", +- "@backstage/plugin-search-backend": "^0.4.2", +- "@backstage/plugin-search-backend-node": "^0.4.5", +- "@backstage/plugin-techdocs-backend": "^0.13.3", +- "@gitbeaker/node": "^34.6.0", +- "@octokit/rest": "^18.5.3", ++ "better-sqlite3": "^8.0.0", + "dockerode": "^3.3.1", +@@ -37,4 +40,3 @@ + "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "@vscode/sqlite3": "^5.0.7", ++ "pg": "^8.3.0", + "winston": "^3.2.1" +@@ -42,6 +44,7 @@ + "devDependencies": { +- "@backstage/cli": "^0.13.2", ++ "@backstage/cli": "^0.24.0-next.1", + "@types/dockerode": "^3.3.0", + "@types/express": "^4.17.6", +- "@types/express-serve-static-core": "^4.17.5" ++ "@types/express-serve-static-core": "^4.17.5", ++ "@types/luxon": "^2.0.4" + }, +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 70bc66b..04c4ff9 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -17,3 +17,3 @@ import { + DatabaseManager, +- SingleHostDiscovery, ++ HostDiscovery, + UrlReaders, +@@ -32,2 +32,3 @@ import { PluginEnvironment } from './types'; + import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; + +@@ -36,7 +37,11 @@ function makeCreateEnv(config: Config) { + const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++ const discovery = HostDiscovery.fromConfig(config); + const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++ const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); + const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); ++ const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); ++ ++ const identity = DefaultIdentityClient.create({ ++ discovery, ++ }); + const permissions = ServerPermissionClient.fromConfig(config, { +@@ -63,2 +68,3 @@ function makeCreateEnv(config: Config) { + permissions, ++ identity, + }; +@@ -106,3 +112,3 @@ module.hot?.accept(); + main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); ++ console.error('Backend failed to start up', error); + process.exit(1); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +index 14e19a1..7c37f68 100644 +--- a/packages/backend/src/plugins/app.ts ++++ b/packages/backend/src/plugins/app.ts +@@ -4,11 +4,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- database, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + return await createRouter({ +- logger, +- config, +- database, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, + appPackageName: 'app', +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +index 015c864..77eb6aa 100644 +--- a/packages/backend/src/plugins/auth.ts ++++ b/packages/backend/src/plugins/auth.ts +@@ -1,2 +1,6 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; ++import { ++ createRouter, ++ providers, ++ defaultAuthProviderFactories, ++} from '@backstage/plugin-auth-backend'; + import { Router } from 'express'; +@@ -4,15 +8,46 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +- tokenManager, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + return await createRouter({ +- logger, +- config, +- database, +- discovery, +- tokenManager, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, ++ providerFactories: { ++ ...defaultAuthProviderFactories, ++ ++ // This replaces the default GitHub auth provider with a customized one. ++ // The `signIn` option enables sign-in for this provider, using the ++ // identity resolution logic that's provided in the `resolver` callback. ++ // ++ // This particular resolver makes all users share a single "guest" identity. ++ // It should only be used for testing and trying out Backstage. ++ // ++ // If you want to use a production ready resolver you can switch to ++ // the one that is commented out below, it looks up a user entity in the ++ // catalog using the GitHub username of the authenticated user. ++ // That resolver requires you to have user entities populated in the catalog, ++ // for example using https://backstage.io/docs/integrations/github/org ++ // ++ // There are other resolvers to choose from, and you can also create ++ // your own, see the auth documentation for more details: ++ // ++ // https://backstage.io/docs/auth/identity-resolver ++ github: providers.github.create({ ++ signIn: { ++ resolver(_, ctx) { ++ const userRef = 'user:default/guest'; // Must be a full entity reference ++ return ctx.issueToken({ ++ claims: { ++ sub: userRef, // The user's own identity ++ ent: [userRef], // A list of identities that the user claims ownership through ++ }, ++ }); ++ }, ++ // resolver: providers.github.resolvers.usernameMatchingUserEntityName(), ++ }, ++ }), ++ }, + }); +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +index 506f6d9..54ec393 100644 +--- a/packages/backend/src/plugins/proxy.ts ++++ b/packages/backend/src/plugins/proxy.ts +@@ -4,8 +4,10 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ }); + } +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +index 6be2e97..a12fee2 100644 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ b/packages/backend/src/plugins/scaffolder.ts +@@ -1,5 +1,3 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; + import { CatalogClient } from '@backstage/catalog-client'; + import { createRouter } from '@backstage/plugin-scaffolder-backend'; +-import Docker from 'dockerode'; + import { Router } from 'express'; +@@ -7,20 +5,17 @@ import type { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- database, +- reader, +- discovery, +-}: PluginEnvironment): Promise { +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- const catalogClient = new CatalogClient({ discoveryApi: discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ const catalogClient = new CatalogClient({ ++ discoveryApi: env.discovery, ++ }); + + return await createRouter({ +- containerRunner, +- logger, +- config, +- database, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ reader: env.reader, + catalogClient, +- reader, ++ identity: env.identity, ++ permissions: env.permissions, + }); +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +index a0a1cc3..e9469dc 100644 +--- a/packages/backend/src/plugins/search.ts ++++ b/packages/backend/src/plugins/search.ts +@@ -7,15 +7,25 @@ import { + import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend'; ++import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend'; ++import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; ++import { Router } from 'express'; + +-export default async function createPlugin({ +- logger, +- permissions, +- discovery, +- config, +- tokenManager, +-}: PluginEnvironment) { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); ++ const searchEngine = new LunrSearchEngine({ ++ logger: env.logger, ++ }); ++ const indexBuilder = new IndexBuilder({ ++ logger: env.logger, ++ searchEngine, ++ }); ++ ++ const schedule = env.scheduler.createScheduledTaskRunner({ ++ frequency: { minutes: 10 }, ++ timeout: { minutes: 15 }, ++ // A 3 second delay gives the backend server a chance to initialize before ++ // any collators are executed, which may attempt requests against the API. ++ initialDelay: { seconds: 3 }, ++ }); + +@@ -24,6 +34,6 @@ export default async function createPlugin({ + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { +- discovery, +- tokenManager, ++ schedule, ++ factory: DefaultCatalogCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, + }), +@@ -33,7 +43,7 @@ export default async function createPlugin({ + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultTechDocsCollator.fromConfig(config, { +- discovery, +- logger, +- tokenManager, ++ schedule, ++ factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ logger: env.logger, ++ tokenManager: env.tokenManager, + }), +@@ -44,6 +54,4 @@ export default async function createPlugin({ + const { scheduler } = await indexBuilder.build(); ++ scheduler.start(); + +- // A 3 second delay gives the backend server a chance to initialize before +- // any collators are executed, which may attempt requests against the API. +- setTimeout(() => scheduler.start(), 3000); + useHotCleanup(module, () => scheduler.stop()); +@@ -53,5 +61,5 @@ export default async function createPlugin({ + types: indexBuilder.getDocumentTypes(), +- permissions, +- config, +- logger, ++ permissions: env.permissions, ++ config: env.config, ++ logger: env.logger, + }); +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +index 054c64d..be8bb0c 100644 +--- a/packages/backend/src/plugins/techdocs.ts ++++ b/packages/backend/src/plugins/techdocs.ts +@@ -11,13 +11,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +- cache, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, ++ const preparers = await Preparers.fromConfig(env.config, { ++ logger: env.logger, ++ reader: env.reader, + }); +@@ -29,4 +25,4 @@ export default async function createPlugin({ + // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, ++ const generators = await Generators.fromConfig(env.config, { ++ logger: env.logger, + containerRunner, +@@ -37,5 +33,5 @@ export default async function createPlugin({ + // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, ++ const publisher = await Publisher.fromConfig(env.config, { ++ logger: env.logger, ++ discovery: env.discovery, + }); +@@ -49,6 +45,6 @@ export default async function createPlugin({ + publisher, +- logger, +- config, +- discovery, +- cache, ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ cache: env.cache, + }); +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +index 0862b0e..9cd2c74 100644 +--- a/packages/backend/src/types.ts ++++ b/packages/backend/src/types.ts +@@ -10,3 +10,4 @@ import { + import { PluginTaskScheduler } from '@backstage/backend-tasks'; +-import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; ++import { PermissionEvaluator } from '@backstage/plugin-permission-common'; ++import { IdentityApi } from '@backstage/plugin-auth-node'; + +@@ -21,3 +22,4 @@ export type PluginEnvironment = { + scheduler: PluginTaskScheduler; +- permissions: PermissionAuthorizer; ++ permissions: PermissionEvaluator; ++ identity: IdentityApi; + }; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..37c7fb1 +--- /dev/null ++++ b/playwright.config.ts +@@ -0,0 +1,60 @@ ++/* ++ * Copyright 2023 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { defineConfig } from '@playwright/test'; ++import { generateProjects } from '@backstage/e2e-test-utils/playwright'; ++ ++/** ++ * See https://playwright.dev/docs/test-configuration. ++ */ ++export default defineConfig({ ++ timeout: 60_000, ++ ++ expect: { ++ timeout: 5_000, ++ }, ++ ++ // Run your local dev server before starting the tests ++ webServer: process.env.CI ++ ? [] ++ : [ ++ { ++ command: 'yarn start', ++ port: 3000, ++ reuseExistingServer: true, ++ timeout: 60_000, ++ }, ++ ], ++ ++ forbidOnly: !!process.env.CI, ++ ++ retries: process.env.CI ? 2 : 0, ++ ++ reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]], ++ ++ use: { ++ actionTimeout: 0, ++ baseURL: ++ process.env.PLAYWRIGHT_URL ?? ++ (process.env.CI ? 'http://localhost:7007' : 'http://localhost:3000'), ++ screenshot: 'only-on-failure', ++ trace: 'on-first-retry', ++ }, ++ ++ outputDir: 'node_modules/.cache/e2e-test-results', ++ ++ projects: generateProjects(), // Find all packages with e2e-test folders ++}); +diff --git a/plugins/README.md b/plugins/README.md +new file mode 100644 +index 0000000..d7865fd +--- /dev/null ++++ b/plugins/README.md +@@ -0,0 +1,9 @@ ++# The Plugins Folder ++ ++This is where your own plugins and their associated modules live, each in a ++separate folder of its own. ++ ++If you want to create a new plugin here, go to your project root directory, run ++the command `yarn new`, and follow the on-screen instructions. ++ ++You can also check out existing plugins on [the plugin marketplace](https://backstage.io/plugins)! +diff --git a/yarn.lock b/yarn.lock +new file mode 100644 +index 0000000..5ad7fe2 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1 @@ ++# intentionally left empty diff --git a/diffs/0.4.20..0.5.7-next.1.diff b/diffs/0.4.20..0.5.7-next.1.diff new file mode 100644 index 00000000..69cd5e33 --- /dev/null +++ b/diffs/0.4.20..0.5.7-next.1.diff @@ -0,0 +1,1659 @@ +diff --git a/.dockerignore b/.dockerignore +index 63c9c34..05edb62 100644 +--- a/.dockerignore ++++ b/.dockerignore +@@ -1,5 +1,8 @@ + .git ++.yarn/cache ++.yarn/install-state.gz + node_modules +-packages +-!packages/backend/dist ++packages/*/src ++packages/*/node_modules + plugins ++*.local.yaml +diff --git a/.eslintignore b/.eslintignore +new file mode 100644 +index 0000000..e5b1994 +--- /dev/null ++++ b/.eslintignore +@@ -0,0 +1 @@ ++playwright.config.ts +diff --git a/.gitignore b/.gitignore +index d16a8d3..fbf8139 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -17,2 +17,11 @@ node_modules/ + ++# Yarn 3 files ++.pnp.* ++.yarn/* ++!.yarn/patches ++!.yarn/plugins ++!.yarn/releases ++!.yarn/sdks ++!.yarn/versions ++ + # Node version directives +@@ -39 +48,7 @@ site + *-credentials.yaml ++ ++# vscode database functionality support files ++*.session.sql ++ ++# E2E test reports ++e2e-test-report/ +diff --git a/app-config.production.yaml b/app-config.production.yaml +index 5e36c23..8f0751c 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -1,3 +1,3 @@ + app: +- # Should be the same as backend.baseUrl when using the `app-backend` plugin ++ # Should be the same as backend.baseUrl when using the `app-backend` plugin. + baseUrl: http://localhost:7007 +@@ -5,4 +5,31 @@ app: + backend: ++ # Note that the baseUrl should be the URL that the browser and other clients ++ # should use when communicating with the backend, i.e. it needs to be ++ # reachable not just from within the backend host, but from all of your ++ # callers. When its value is "http://localhost:7007", it's strictly private ++ # and can't be reached by others. + baseUrl: http://localhost:7007 +- listen: +- port: 7007 ++ # The listener can also be expressed as a single : string. In this case we bind to ++ # all interfaces, the most permissive setting. The right value depends on your specific deployment. ++ listen: ':7007' ++ ++ # config options: https://node-postgres.com/api/client ++ database: ++ client: pg ++ connection: ++ host: ${POSTGRES_HOST} ++ port: ${POSTGRES_PORT} ++ user: ${POSTGRES_USER} ++ password: ${POSTGRES_PASSWORD} ++ # https://node-postgres.com/features/ssl ++ # you can set the sslmode configuration option via the `PGSSLMODE` environment variable ++ # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require) ++ # ssl: ++ # ca: # if you have a CA file and want to verify it you can uncomment this section ++ # $file: /ca/server.crt ++ ++catalog: ++ # Overrides the default list locations from app-config.yaml as these contain example data. ++ # See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details ++ # on how to get entities into the catalog. ++ locations: [] +diff --git a/app-config.yaml b/app-config.yaml +index d45e354..08d2abf 100644 +--- a/app-config.yaml ++++ b/app-config.yaml +@@ -9,3 +9,3 @@ backend: + # Used for enabling authentication, secret is shared by all backend plugins +- # See https://backstage.io/docs/tutorials/backend-to-backend-auth for ++ # See https://backstage.io/docs/auth/service-to-service-auth for + # information on the format +@@ -17,2 +17,4 @@ backend: + port: 7007 ++ # Uncomment the following host directive to bind to specific interfaces ++ # host: 127.0.0.1 + csp: +@@ -23,9 +25,9 @@ backend: + origin: http://localhost:3000 +- methods: [GET, POST, PUT, DELETE] ++ methods: [GET, HEAD, PATCH, POST, PUT, DELETE] + credentials: true ++ # This is for local development only, it is not recommended to use this in production ++ # The production database configuration is stored in app-config.production.yaml + database: +- client: sqlite3 ++ client: better-sqlite3 + connection: ':memory:' +- cache: +- store: memory + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir +@@ -35,2 +37,4 @@ integrations: + - host: github.com ++ # This is a Personal Access Token or PAT from GitHub. You can find out how to generate this token, and more information ++ # about setting up the GitHub integration here: https://backstage.io/docs/getting-started/configuration#setting-up-a-github-integration + token: ${GITHUB_TOKEN} +@@ -42,5 +46,8 @@ integrations: + proxy: +- '/test': +- target: 'https://example.com' +- changeOrigin: true ++ ### Example for how to add a proxy endpoint for the frontend. ++ ### A typical reason to do this is to handle HTTPS and CORS for internal services. ++ # endpoints: ++ # '/test': ++ # target: 'https://example.com' ++ # changeOrigin: true + +@@ -69,40 +76,28 @@ catalog: + rules: +- - allow: [Component, System, API, Group, User, Resource, Location] ++ - allow: [Component, System, API, Resource, Location] + locations: +- # Backstage example components +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-components.yaml ++ # Local example data, file locations are relative to the backend process, typically `packages/backend` ++ - type: file ++ target: ../../examples/entities.yaml + +- # Backstage example systems +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-systems.yaml +- +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml +- +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml +- +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.yaml +- +- # Backstage example templates +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/react-ssr-template/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/springboot-grpc-template/template.yaml ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml + rules: + - allow: [Template] +- - type: url +- target: https://github.com/spotify/cookiecutter-golang/blob/master/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/docs-template/template.yaml ++ ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml + rules: +- - allow: [Template] ++ - allow: [User, Group] ++ ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml ++ ++ ## Uncomment these lines to add an example org ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme-corp.yaml ++ # rules: ++ # - allow: [User, Group] +diff --git a/backstage.json b/backstage.json +index 06a76a3..707af29 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "0.4.20" ++ "version": "1.20.0-next.1" + } +diff --git a/examples/entities.yaml b/examples/entities.yaml +new file mode 100644 +index 0000000..447e8b1 +--- /dev/null ++++ b/examples/entities.yaml +@@ -0,0 +1,41 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system ++apiVersion: backstage.io/v1alpha1 ++kind: System ++metadata: ++ name: examples ++spec: ++ owner: guests ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: example-website ++spec: ++ type: website ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ providesApis: [example-grpc-api] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api ++apiVersion: backstage.io/v1alpha1 ++kind: API ++metadata: ++ name: example-grpc-api ++spec: ++ type: grpc ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ definition: | ++ syntax = "proto3"; ++ ++ service Exampler { ++ rpc Example (ExampleMessage) returns (ExampleMessage) {}; ++ } ++ ++ message ExampleMessage { ++ string example = 1; ++ }; +diff --git a/examples/org.yaml b/examples/org.yaml +new file mode 100644 +index 0000000..a10e81f +--- /dev/null ++++ b/examples/org.yaml +@@ -0,0 +1,17 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user ++apiVersion: backstage.io/v1alpha1 ++kind: User ++metadata: ++ name: guest ++spec: ++ memberOf: [guests] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group ++apiVersion: backstage.io/v1alpha1 ++kind: Group ++metadata: ++ name: guests ++spec: ++ type: team ++ children: [] +diff --git a/examples/template/content/catalog-info.yaml b/examples/template/content/catalog-info.yaml +new file mode 100644 +index 0000000..d4ccca4 +--- /dev/null ++++ b/examples/template/content/catalog-info.yaml +@@ -0,0 +1,8 @@ ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: ${{ values.name | dump }} ++spec: ++ type: service ++ owner: user:guest ++ lifecycle: experimental +diff --git a/examples/template/content/index.js b/examples/template/content/index.js +new file mode 100644 +index 0000000..071ce5a +--- /dev/null ++++ b/examples/template/content/index.js +@@ -0,0 +1 @@ ++console.log('Hello from ${{ values.name }}!'); +diff --git a/examples/template/content/package.json b/examples/template/content/package.json +new file mode 100644 +index 0000000..86f968a +--- /dev/null ++++ b/examples/template/content/package.json +@@ -0,0 +1,5 @@ ++{ ++ "name": "${{ values.name }}", ++ "private": true, ++ "dependencies": {} ++} +diff --git a/examples/template/template.yaml b/examples/template/template.yaml +new file mode 100644 +index 0000000..33f262b +--- /dev/null ++++ b/examples/template/template.yaml +@@ -0,0 +1,74 @@ ++apiVersion: scaffolder.backstage.io/v1beta3 ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template ++kind: Template ++metadata: ++ name: example-nodejs-template ++ title: Example Node.js Template ++ description: An example template for the scaffolder that creates a simple Node.js service ++spec: ++ owner: user:guest ++ type: service ++ ++ # These parameters are used to generate the input form in the frontend, and are ++ # used to gather input data for the execution of the template. ++ parameters: ++ - title: Fill in some steps ++ required: ++ - name ++ properties: ++ name: ++ title: Name ++ type: string ++ description: Unique name of the component ++ ui:autofocus: true ++ ui:options: ++ rows: 5 ++ - title: Choose a location ++ required: ++ - repoUrl ++ properties: ++ repoUrl: ++ title: Repository Location ++ type: string ++ ui:field: RepoUrlPicker ++ ui:options: ++ allowedHosts: ++ - github.com ++ ++ # These steps are executed in the scaffolder backend, using data that we gathered ++ # via the parameters above. ++ steps: ++ # Each step executes an action, in this case one templates files into the working directory. ++ - id: fetch-base ++ name: Fetch Base ++ action: fetch:template ++ input: ++ url: ./content ++ values: ++ name: ${{ parameters.name }} ++ ++ # This step publishes the contents of the working directory to GitHub. ++ - id: publish ++ name: Publish ++ action: publish:github ++ input: ++ allowedHosts: ['github.com'] ++ description: This is ${{ parameters.name }} ++ repoUrl: ${{ parameters.repoUrl }} ++ ++ # The final step is to register our new component in the catalog. ++ - id: register ++ name: Register ++ action: catalog:register ++ input: ++ repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} ++ catalogInfoPath: '/catalog-info.yaml' ++ ++ # Outputs are displayed to the user after a successful execution of the template. ++ output: ++ links: ++ - title: Repository ++ url: ${{ steps['publish'].output.remoteUrl }} ++ - title: Open in catalog ++ icon: catalog ++ entityRef: ${{ steps['register'].output.entityRef }} +diff --git a/lerna.json b/lerna.json +index 322929d..529a62f 100644 +--- a/lerna.json ++++ b/lerna.json +@@ -3,4 +3,4 @@ + "npmClient": "yarn", +- "useWorkspaces": true, +- "version": "0.1.0" ++ "version": "0.1.0", ++ "$schema": "node_modules/lerna/schemas/lerna-schema.json" + } +diff --git a/package.json b/package.json +index 8005020..2364da8 100644 +--- a/package.json ++++ b/package.json +@@ -5,3 +5,3 @@ + "engines": { +- "node": "14 || 16" ++ "node": "18 || 20" + }, +@@ -11,3 +11,4 @@ + "start-backend": "yarn workspace backend start", +- "build": "lerna run build", ++ "build:backend": "yarn workspace backend build", ++ "build:all": "backstage-cli repo build --all", + "build-image": "yarn workspace backend build-image", +@@ -15,11 +16,11 @@ + "tsc:full": "tsc --skipLibCheck false --incremental false", +- "clean": "backstage-cli clean && lerna run clean", +- "diff": "lerna run diff --", +- "test": "backstage-cli test", +- "test:all": "lerna run test -- --coverage", +- "lint": "lerna run lint --since origin/master --", +- "lint:all": "lerna run lint --", ++ "clean": "backstage-cli repo clean", ++ "test": "backstage-cli repo test", ++ "test:all": "backstage-cli repo test --coverage", ++ "test:e2e": "playwright test", ++ "fix": "backstage-cli repo fix", ++ "lint": "backstage-cli repo lint --since origin/master", ++ "lint:all": "backstage-cli repo lint", + "prettier:check": "prettier --check .", +- "create-plugin": "backstage-cli create-plugin --scope internal", +- "remove-plugin": "backstage-cli remove-plugin" ++ "new": "backstage-cli new --scope internal" + }, +@@ -32,8 +33,15 @@ + "devDependencies": { +- "@backstage/cli": "^0.14.0", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/e2e-test-utils": "^0.1.0", ++ "@playwright/test": "^1.32.3", + "@spotify/prettier-config": "^12.0.0", +- "concurrently": "^6.0.0", +- "lerna": "^4.0.0", ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^9.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.5.4" ++ "typescript": "~5.2.0" ++ }, ++ "resolutions": { ++ "@types/react": "^17", ++ "@types/react-dom": "^17" + }, +diff --git a/packages/README.md b/packages/README.md +new file mode 100644 +index 0000000..6327fa0 +--- /dev/null ++++ b/packages/README.md +@@ -0,0 +1,9 @@ ++# The Packages Folder ++ ++This is where your own applications and centrally managed libraries live, each ++in a separate folder of its own. ++ ++From the start there's an `app` folder (for the frontend) and a `backend` folder ++(for the Node backend), but you can also add more modules in here that house ++your core additions and adaptations, such as themes, common React component ++libraries, utilities, and similar. +diff --git a/packages/app/.eslintignore b/packages/app/.eslintignore +new file mode 100644 +index 0000000..a48cf0d +--- /dev/null ++++ b/packages/app/.eslintignore +@@ -0,0 +1 @@ ++public +diff --git a/packages/app/.eslintrc.js b/packages/app/.eslintrc.js +index 13573ef..e2a53a6 100644 +--- a/packages/app/.eslintrc.js ++++ b/packages/app/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/app/cypress.json b/packages/app/cypress.json +deleted file mode 100644 +index 5de7ebf..0000000 +--- a/packages/app/cypress.json ++++ /dev/null +@@ -1,5 +0,0 @@ +-{ +- "baseUrl": "http://localhost:3001", +- "fixturesFolder": false, +- "pluginsFile": false +-} +diff --git a/packages/app/cypress/.eslintrc.json b/packages/app/cypress/.eslintrc.json +deleted file mode 100644 +index 2b3a458..0000000 +--- a/packages/app/cypress/.eslintrc.json ++++ /dev/null +@@ -1,21 +0,0 @@ +-{ +- "plugins": ["cypress"], +- "extends": ["plugin:cypress/recommended"], +- "rules": { +- "jest/expect-expect": [ +- "error", +- { +- "assertFunctionNames": ["expect", "cy.contains"] +- } +- ], +- "import/no-extraneous-dependencies": [ +- "error", +- { +- "devDependencies": true, +- "optionalDependencies": true, +- "peerDependencies": true, +- "bundledDependencies": true +- } +- ] +- } +-} +diff --git a/packages/app/cypress/integration/app.js b/packages/app/cypress/integration/app.js +deleted file mode 100644 +index 43fb2e3..0000000 +--- a/packages/app/cypress/integration/app.js ++++ /dev/null +@@ -1,6 +0,0 @@ +-describe('App', () => { +- it('should render the catalog', () => { +- cy.visit('/'); +- cy.contains('My Company Catalog'); +- }); +-}); +diff --git a/packages/app/e2e-tests/app.test.ts b/packages/app/e2e-tests/app.test.ts +new file mode 100644 +index 0000000..d45bc0d +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,23 @@ ++/* ++ * Copyright 2020 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { test, expect } from '@playwright/test'; ++ ++test('App should render the welcome page', async ({ page }) => { ++ await page.goto('/'); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 6022dc0..2100405 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,25 +5,38 @@ + "bundled": true, ++ "backstage": { ++ "role": "frontend" ++ }, ++ "scripts": { ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "clean": "backstage-cli package clean", ++ "test": "backstage-cli package test", ++ "lint": "backstage-cli package lint" ++ }, + "dependencies": { +- "@backstage/app-defaults": "^0.1.8", +- "@backstage/catalog-model": "^0.10.0", +- "@backstage/cli": "^0.14.0", +- "@backstage/core-app-api": "^0.5.3", +- "@backstage/core-components": "^0.8.9", +- "@backstage/core-plugin-api": "^0.6.1", +- "@backstage/integration-react": "^0.1.22", +- "@backstage/plugin-api-docs": "^0.7.3", +- "@backstage/plugin-catalog": "^0.8.0", +- "@backstage/plugin-catalog-common": "^0.1.3", +- "@backstage/plugin-catalog-graph": "^0.2.11", +- "@backstage/plugin-catalog-import": "^0.8.2", +- "@backstage/plugin-catalog-react": "^0.6.15", +- "@backstage/plugin-github-actions": "^0.4.36", +- "@backstage/plugin-org": "^0.4.3", +- "@backstage/plugin-permission-react": "^0.3.1", +- "@backstage/plugin-scaffolder": "^0.12.3", +- "@backstage/plugin-search": "^0.7.0", +- "@backstage/plugin-tech-radar": "^0.5.6", +- "@backstage/plugin-techdocs": "^0.13.4", +- "@backstage/plugin-user-settings": "^0.3.20", +- "@backstage/theme": "^0.2.15", ++ "@backstage/app-defaults": "^1.4.5-next.1", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/core-app-api": "^1.11.1-next.0", ++ "@backstage/core-components": "^0.13.8-next.1", ++ "@backstage/core-plugin-api": "^1.8.0-next.0", ++ "@backstage/integration-react": "^1.1.21-next.1", ++ "@backstage/plugin-api-docs": "^0.9.13-next.1", ++ "@backstage/plugin-catalog": "^1.15.0-next.1", ++ "@backstage/plugin-catalog-common": "^1.0.17", ++ "@backstage/plugin-catalog-graph": "^0.2.38-next.1", ++ "@backstage/plugin-catalog-import": "^0.10.2-next.1", ++ "@backstage/plugin-catalog-react": "^1.9.0-next.1", ++ "@backstage/plugin-github-actions": "^0.6.7-next.1", ++ "@backstage/plugin-org": "^0.6.16-next.1", ++ "@backstage/plugin-permission-react": "^0.4.17-next.0", ++ "@backstage/plugin-scaffolder": "^1.16.0-next.1", ++ "@backstage/plugin-search": "^1.4.2-next.1", ++ "@backstage/plugin-search-react": "^1.7.2-next.1", ++ "@backstage/plugin-tech-radar": "^0.6.10-next.1", ++ "@backstage/plugin-techdocs": "^1.8.1-next.1", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.2-next.1", ++ "@backstage/plugin-techdocs-react": "^1.1.13-next.1", ++ "@backstage/plugin-user-settings": "^0.7.12-next.1", ++ "@backstage/theme": "^0.4.4-next.0", + "@material-ui/core": "^4.12.2", +@@ -33,29 +46,15 @@ + "react-dom": "^17.0.2", +- "react-router": "6.0.0-beta.0", +- "react-router-dom": "6.0.0-beta.0", +- "react-use": "^15.3.3" ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^0.2.5", ++ "@backstage/test-utils": "^1.4.5-next.0", ++ "@playwright/test": "^1.32.3", + "@testing-library/jest-dom": "^5.10.1", +- "@testing-library/react": "^10.4.1", +- "@testing-library/user-event": "^12.0.7", +- "@types/jest": "^26.0.7", +- "@types/node": "^14.14.32", ++ "@testing-library/react": "^12.1.3", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^8.0.0", + "@types/react-dom": "*", +- "cross-env": "^7.0.0", +- "cypress": "^7.3.0", +- "eslint-plugin-cypress": "^2.10.3", +- "start-server-and-test": "^1.10.11" +- }, +- "scripts": { +- "start": "backstage-cli app:serve", +- "build": "backstage-cli app:build", +- "clean": "backstage-cli clean", +- "test": "backstage-cli test", +- "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", +- "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", +- "lint": "backstage-cli lint", +- "cy:dev": "cypress open", +- "cy:run": "cypress run" ++ "cross-env": "^7.0.0" + }, +diff --git a/packages/app/public/index.html b/packages/app/public/index.html +index a936c73..fc758ee 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -10,3 +10,2 @@ + /> +- + +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 9b65186..8d62f29 100644 +--- a/packages/app/src/App.tsx ++++ b/packages/app/src/App.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { Navigate, Route } from 'react-router'; ++import { Navigate, Route } from 'react-router-dom'; + import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; +@@ -17,3 +17,2 @@ import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -22,2 +21,4 @@ import { + } from '@backstage/plugin-techdocs'; ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; + import { UserSettingsPage } from '@backstage/plugin-user-settings'; +@@ -30,6 +31,6 @@ import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; + import { createApp } from '@backstage/app-defaults'; +-import { FlatRoutes } from '@backstage/core-app-api'; ++import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; + import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; +-import { PermissionedRoute } from '@backstage/plugin-permission-react'; +-import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common'; ++import { RequirePermission } from '@backstage/plugin-permission-react'; ++import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; + +@@ -41,2 +42,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -47,2 +49,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -54,8 +57,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -67,5 +67,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -80,6 +82,9 @@ const routes = ( + /> +- } ++ element={ ++ ++ ++ ++ } + /> +@@ -93,4 +98,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -100,5 +105,3 @@ const App = () => ( + +- ++ , + ); +- +-export default App; +diff --git a/packages/app/src/components/Root/LogoFull.tsx b/packages/app/src/components/Root/LogoFull.tsx +index c7b1c84..47e3b73 100644 +--- a/packages/app/src/components/Root/LogoFull.tsx ++++ b/packages/app/src/components/Root/LogoFull.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/LogoIcon.tsx b/packages/app/src/components/Root/LogoIcon.tsx +index 073cf6e..7eae8c7 100644 +--- a/packages/app/src/components/Root/LogoIcon.tsx ++++ b/packages/app/src/components/Root/LogoIcon.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx +index 90738c4..6768b48 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,19 +1,3 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- +-import React, { useContext, PropsWithChildren } from 'react'; +-import { Link, makeStyles } from '@material-ui/core'; ++import React, { PropsWithChildren } from 'react'; ++import { makeStyles } from '@material-ui/core'; + import HomeIcon from '@material-ui/icons/Home'; +@@ -25,3 +9,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -34,3 +17,2 @@ import { + sidebarConfig, +- SidebarContext, + SidebarDivider, +@@ -41,2 +23,4 @@ import { + SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -62,3 +46,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -66,8 +50,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +diff --git a/packages/app/src/components/Root/index.ts b/packages/app/src/components/Root/index.ts +index dff706f..3528881 100644 +--- a/packages/app/src/components/Root/index.ts ++++ b/packages/app/src/components/Root/index.ts +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + export { Root } from './Root'; +diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx +index 84d0944..6722ea2 100644 +--- a/packages/app/src/components/catalog/EntityPage.tsx ++++ b/packages/app/src/components/catalog/EntityPage.tsx +@@ -1,16 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ + import React from 'react'; +@@ -42,2 +27,4 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +@@ -70,2 +57,13 @@ import { + ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); ++ + const cicdContent = ( +@@ -107,2 +105,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -169,3 +175,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -196,3 +202,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -215,3 +221,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -315,3 +321,6 @@ const systemPage = ( + +- ++ ++ ++ ++ + +diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx +index a88e725..1788dde 100644 +--- a/packages/app/src/components/search/SearchPage.tsx ++++ b/packages/app/src/components/search/SearchPage.tsx +@@ -1,5 +1,5 @@ + import React from 'react'; +-import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; ++import { makeStyles, Theme, Grid, Paper } from '@material-ui/core'; + +-import { CatalogResultListItem } from '@backstage/plugin-catalog'; ++import { CatalogSearchResultListItem } from '@backstage/plugin-catalog'; + import { +@@ -8,4 +8,5 @@ import { + } from '@backstage/plugin-catalog-react'; +-import { DocsResultListItem } from '@backstage/plugin-techdocs'; ++import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; + ++import { SearchType } from '@backstage/plugin-search'; + import { +@@ -14,6 +15,5 @@ import { + SearchResult, +- SearchType, +- DefaultResultListItem, ++ SearchPagination, + useSearch, +-} from '@backstage/plugin-search'; ++} from '@backstage/plugin-search-react'; + import { +@@ -111,32 +111,6 @@ const SearchPage = () => { + ++ + +- {({ results }) => ( +- +- {results.map(({ type, document }) => { +- switch (type) { +- case 'software-catalog': +- return ( +- +- ); +- case 'techdocs': +- return ( +- +- ); +- default: +- return ( +- +- ); +- } +- })} +- +- )} ++ } /> ++ } /> + +diff --git a/packages/backend/.eslintrc.js b/packages/backend/.eslintrc.js +index 16a033d..e2a53a6 100644 +--- a/packages/backend/.eslintrc.js ++++ b/packages/backend/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint.backend')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index 31231a3..18548e9 100644 +--- a/packages/backend/Dockerfile ++++ b/packages/backend/Dockerfile +@@ -7,3 +7,3 @@ + # yarn tsc +-# yarn build ++# yarn build:backend + # +@@ -11,6 +11,29 @@ + +-FROM node:14-buster-slim ++FROM node:18-bookworm-slim + ++# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends python3 g++ build-essential && \ ++ yarn config set python /usr/bin/python3 ++ ++# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, ++# in which case you should also move better-sqlite3 to "devDependencies" in package.json. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends libsqlite3-dev ++ ++# From here on we use the least-privileged `node` user to run the backend. ++USER node ++ ++# This should create the app dir as `node`. ++# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. ++# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. + WORKDIR /app + ++# This switches many Node.js dependencies to production mode. ++ENV NODE_ENV production ++ + # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. +@@ -18,11 +41,12 @@ WORKDIR /app + # and along with yarn.lock and the root package.json, that's enough to run yarn install. +-COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ ++COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ + RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz + +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + + # Then copy the rest of the backend bundle, along with any other files we might want. +-COPY packages/backend/dist/bundle.tar.gz app-config.yaml ./ ++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"] +diff --git a/packages/backend/README.md b/packages/backend/README.md +index 02426ef..3607b0a 100644 +--- a/packages/backend/README.md ++++ b/packages/backend/README.md +@@ -18,4 +18,2 @@ To run the example backend, first go to the project root and run + yarn install +-yarn tsc +-yarn build + ``` +@@ -27,7 +25,2 @@ After that, go to the `packages/backend` directory and run + ```bash +-AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x \ +-AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x \ +-AUTH_OAUTH2_CLIENT_ID=x AUTH_OAUTH2_CLIENT_SECRET=x \ +-AUTH_OAUTH2_AUTH_URL=x AUTH_OAUTH2_TOKEN_URL=x \ +-LOG_LEVEL=debug \ + yarn start +@@ -35,4 +28,4 @@ yarn start + +-Substitute `x` for actual values, or leave them as dummy values just to try out +-the backend without using the auth or sentry features. ++If you want to override any configuration locally, for example adding any secrets, ++you can do so in `app-config.local.yaml`. + +@@ -45,3 +38,3 @@ locations to the backend. These are places where the backend can find some + entity descriptor data to consume and serve. For more information, see +-[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog). ++[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). + +@@ -65,2 +58,2 @@ and + - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +-- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) ++- [Backstage Documentation](https://backstage.io/docs) +diff --git a/packages/backend/package.json b/packages/backend/package.json +index f018d52..f224eaf 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,30 +6,33 @@ + "private": true, ++ "backstage": { ++ "role": "backend" ++ }, + "scripts": { +- "build": "backstage-cli backend:bundle", +- "build-image": "docker build ../.. -f Dockerfile --tag backstage", +- "start": "backstage-cli backend:dev", +- "lint": "backstage-cli lint", +- "test": "backstage-cli test", +- "clean": "backstage-cli clean", +- "migrate:create": "knex migrate:make -x ts" ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "lint": "backstage-cli package lint", ++ "test": "backstage-cli package test", ++ "clean": "backstage-cli package clean", ++ "build-image": "docker build ../.. -f Dockerfile --tag backstage" + }, + "dependencies": { ++ "@backstage/backend-common": "^0.19.9-next.1", ++ "@backstage/backend-tasks": "^0.5.12-next.1", ++ "@backstage/catalog-client": "^1.4.5", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/config": "^1.1.1", ++ "@backstage/plugin-app-backend": "^0.3.55-next.1", ++ "@backstage/plugin-auth-backend": "^0.20.0-next.1", ++ "@backstage/plugin-auth-node": "^0.4.1-next.1", ++ "@backstage/plugin-catalog-backend": "^1.15.0-next.1", ++ "@backstage/plugin-permission-common": "^0.7.9", ++ "@backstage/plugin-permission-node": "^0.7.18-next.1", ++ "@backstage/plugin-proxy-backend": "^0.4.5-next.1", ++ "@backstage/plugin-scaffolder-backend": "^1.19.0-next.1", ++ "@backstage/plugin-search-backend": "^1.4.7-next.1", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.16-next.1", ++ "@backstage/plugin-search-backend-node": "^1.2.11-next.1", ++ "@backstage/plugin-techdocs-backend": "^1.9.0-next.1", + "app": "link:../app", +- "@backstage/backend-common": "^0.10.8", +- "@backstage/backend-tasks": "^0.1.7", +- "@backstage/catalog-model": "^0.10.0", +- "@backstage/catalog-client": "^0.7.0", +- "@backstage/config": "^0.1.14", +- "@backstage/plugin-app-backend": "^0.3.25", +- "@backstage/plugin-auth-backend": "^0.10.1", +- "@backstage/plugin-catalog-backend": "^0.21.4", +- "@backstage/plugin-permission-common": "^0.5.0", +- "@backstage/plugin-permission-node": "^0.5.0", +- "@backstage/plugin-proxy-backend": "^0.2.19", +- "@backstage/plugin-scaffolder-backend": "^0.16.0", +- "@backstage/plugin-search-backend": "^0.4.3", +- "@backstage/plugin-search-backend-node": "^0.4.6", +- "@backstage/plugin-techdocs-backend": "^0.13.4", +- "@gitbeaker/node": "^34.6.0", +- "@octokit/rest": "^18.5.3", ++ "better-sqlite3": "^8.0.0", + "dockerode": "^3.3.1", +@@ -37,4 +40,3 @@ + "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "@vscode/sqlite3": "^5.0.7", ++ "pg": "^8.3.0", + "winston": "^3.2.1" +@@ -42,6 +44,7 @@ + "devDependencies": { +- "@backstage/cli": "^0.14.0", ++ "@backstage/cli": "^0.24.0-next.1", + "@types/dockerode": "^3.3.0", + "@types/express": "^4.17.6", +- "@types/express-serve-static-core": "^4.17.5" ++ "@types/express-serve-static-core": "^4.17.5", ++ "@types/luxon": "^2.0.4" + }, +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 70bc66b..04c4ff9 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -17,3 +17,3 @@ import { + DatabaseManager, +- SingleHostDiscovery, ++ HostDiscovery, + UrlReaders, +@@ -32,2 +32,3 @@ import { PluginEnvironment } from './types'; + import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; + +@@ -36,7 +37,11 @@ function makeCreateEnv(config: Config) { + const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++ const discovery = HostDiscovery.fromConfig(config); + const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++ const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); + const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); ++ const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); ++ ++ const identity = DefaultIdentityClient.create({ ++ discovery, ++ }); + const permissions = ServerPermissionClient.fromConfig(config, { +@@ -63,2 +68,3 @@ function makeCreateEnv(config: Config) { + permissions, ++ identity, + }; +@@ -106,3 +112,3 @@ module.hot?.accept(); + main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); ++ console.error('Backend failed to start up', error); + process.exit(1); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +index 14e19a1..7c37f68 100644 +--- a/packages/backend/src/plugins/app.ts ++++ b/packages/backend/src/plugins/app.ts +@@ -4,11 +4,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- database, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + return await createRouter({ +- logger, +- config, +- database, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, + appPackageName: 'app', +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +index 015c864..77eb6aa 100644 +--- a/packages/backend/src/plugins/auth.ts ++++ b/packages/backend/src/plugins/auth.ts +@@ -1,2 +1,6 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; ++import { ++ createRouter, ++ providers, ++ defaultAuthProviderFactories, ++} from '@backstage/plugin-auth-backend'; + import { Router } from 'express'; +@@ -4,15 +8,46 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +- tokenManager, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + return await createRouter({ +- logger, +- config, +- database, +- discovery, +- tokenManager, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, ++ providerFactories: { ++ ...defaultAuthProviderFactories, ++ ++ // This replaces the default GitHub auth provider with a customized one. ++ // The `signIn` option enables sign-in for this provider, using the ++ // identity resolution logic that's provided in the `resolver` callback. ++ // ++ // This particular resolver makes all users share a single "guest" identity. ++ // It should only be used for testing and trying out Backstage. ++ // ++ // If you want to use a production ready resolver you can switch to ++ // the one that is commented out below, it looks up a user entity in the ++ // catalog using the GitHub username of the authenticated user. ++ // That resolver requires you to have user entities populated in the catalog, ++ // for example using https://backstage.io/docs/integrations/github/org ++ // ++ // There are other resolvers to choose from, and you can also create ++ // your own, see the auth documentation for more details: ++ // ++ // https://backstage.io/docs/auth/identity-resolver ++ github: providers.github.create({ ++ signIn: { ++ resolver(_, ctx) { ++ const userRef = 'user:default/guest'; // Must be a full entity reference ++ return ctx.issueToken({ ++ claims: { ++ sub: userRef, // The user's own identity ++ ent: [userRef], // A list of identities that the user claims ownership through ++ }, ++ }); ++ }, ++ // resolver: providers.github.resolvers.usernameMatchingUserEntityName(), ++ }, ++ }), ++ }, + }); +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +index 506f6d9..54ec393 100644 +--- a/packages/backend/src/plugins/proxy.ts ++++ b/packages/backend/src/plugins/proxy.ts +@@ -4,8 +4,10 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ }); + } +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +index 6be2e97..a12fee2 100644 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ b/packages/backend/src/plugins/scaffolder.ts +@@ -1,5 +1,3 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; + import { CatalogClient } from '@backstage/catalog-client'; + import { createRouter } from '@backstage/plugin-scaffolder-backend'; +-import Docker from 'dockerode'; + import { Router } from 'express'; +@@ -7,20 +5,17 @@ import type { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- database, +- reader, +- discovery, +-}: PluginEnvironment): Promise { +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- const catalogClient = new CatalogClient({ discoveryApi: discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ const catalogClient = new CatalogClient({ ++ discoveryApi: env.discovery, ++ }); + + return await createRouter({ +- containerRunner, +- logger, +- config, +- database, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ reader: env.reader, + catalogClient, +- reader, ++ identity: env.identity, ++ permissions: env.permissions, + }); +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +index a0a1cc3..e9469dc 100644 +--- a/packages/backend/src/plugins/search.ts ++++ b/packages/backend/src/plugins/search.ts +@@ -7,15 +7,25 @@ import { + import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend'; ++import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend'; ++import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; ++import { Router } from 'express'; + +-export default async function createPlugin({ +- logger, +- permissions, +- discovery, +- config, +- tokenManager, +-}: PluginEnvironment) { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); ++ const searchEngine = new LunrSearchEngine({ ++ logger: env.logger, ++ }); ++ const indexBuilder = new IndexBuilder({ ++ logger: env.logger, ++ searchEngine, ++ }); ++ ++ const schedule = env.scheduler.createScheduledTaskRunner({ ++ frequency: { minutes: 10 }, ++ timeout: { minutes: 15 }, ++ // A 3 second delay gives the backend server a chance to initialize before ++ // any collators are executed, which may attempt requests against the API. ++ initialDelay: { seconds: 3 }, ++ }); + +@@ -24,6 +34,6 @@ export default async function createPlugin({ + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { +- discovery, +- tokenManager, ++ schedule, ++ factory: DefaultCatalogCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, + }), +@@ -33,7 +43,7 @@ export default async function createPlugin({ + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultTechDocsCollator.fromConfig(config, { +- discovery, +- logger, +- tokenManager, ++ schedule, ++ factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ logger: env.logger, ++ tokenManager: env.tokenManager, + }), +@@ -44,6 +54,4 @@ export default async function createPlugin({ + const { scheduler } = await indexBuilder.build(); ++ scheduler.start(); + +- // A 3 second delay gives the backend server a chance to initialize before +- // any collators are executed, which may attempt requests against the API. +- setTimeout(() => scheduler.start(), 3000); + useHotCleanup(module, () => scheduler.stop()); +@@ -53,5 +61,5 @@ export default async function createPlugin({ + types: indexBuilder.getDocumentTypes(), +- permissions, +- config, +- logger, ++ permissions: env.permissions, ++ config: env.config, ++ logger: env.logger, + }); +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +index 054c64d..be8bb0c 100644 +--- a/packages/backend/src/plugins/techdocs.ts ++++ b/packages/backend/src/plugins/techdocs.ts +@@ -11,13 +11,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +- cache, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, ++ const preparers = await Preparers.fromConfig(env.config, { ++ logger: env.logger, ++ reader: env.reader, + }); +@@ -29,4 +25,4 @@ export default async function createPlugin({ + // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, ++ const generators = await Generators.fromConfig(env.config, { ++ logger: env.logger, + containerRunner, +@@ -37,5 +33,5 @@ export default async function createPlugin({ + // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, ++ const publisher = await Publisher.fromConfig(env.config, { ++ logger: env.logger, ++ discovery: env.discovery, + }); +@@ -49,6 +45,6 @@ export default async function createPlugin({ + publisher, +- logger, +- config, +- discovery, +- cache, ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ cache: env.cache, + }); +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +index 0862b0e..9cd2c74 100644 +--- a/packages/backend/src/types.ts ++++ b/packages/backend/src/types.ts +@@ -10,3 +10,4 @@ import { + import { PluginTaskScheduler } from '@backstage/backend-tasks'; +-import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; ++import { PermissionEvaluator } from '@backstage/plugin-permission-common'; ++import { IdentityApi } from '@backstage/plugin-auth-node'; + +@@ -21,3 +22,4 @@ export type PluginEnvironment = { + scheduler: PluginTaskScheduler; +- permissions: PermissionAuthorizer; ++ permissions: PermissionEvaluator; ++ identity: IdentityApi; + }; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..37c7fb1 +--- /dev/null ++++ b/playwright.config.ts +@@ -0,0 +1,60 @@ ++/* ++ * Copyright 2023 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { defineConfig } from '@playwright/test'; ++import { generateProjects } from '@backstage/e2e-test-utils/playwright'; ++ ++/** ++ * See https://playwright.dev/docs/test-configuration. ++ */ ++export default defineConfig({ ++ timeout: 60_000, ++ ++ expect: { ++ timeout: 5_000, ++ }, ++ ++ // Run your local dev server before starting the tests ++ webServer: process.env.CI ++ ? [] ++ : [ ++ { ++ command: 'yarn start', ++ port: 3000, ++ reuseExistingServer: true, ++ timeout: 60_000, ++ }, ++ ], ++ ++ forbidOnly: !!process.env.CI, ++ ++ retries: process.env.CI ? 2 : 0, ++ ++ reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]], ++ ++ use: { ++ actionTimeout: 0, ++ baseURL: ++ process.env.PLAYWRIGHT_URL ?? ++ (process.env.CI ? 'http://localhost:7007' : 'http://localhost:3000'), ++ screenshot: 'only-on-failure', ++ trace: 'on-first-retry', ++ }, ++ ++ outputDir: 'node_modules/.cache/e2e-test-results', ++ ++ projects: generateProjects(), // Find all packages with e2e-test folders ++}); +diff --git a/plugins/README.md b/plugins/README.md +new file mode 100644 +index 0000000..d7865fd +--- /dev/null ++++ b/plugins/README.md +@@ -0,0 +1,9 @@ ++# The Plugins Folder ++ ++This is where your own plugins and their associated modules live, each in a ++separate folder of its own. ++ ++If you want to create a new plugin here, go to your project root directory, run ++the command `yarn new`, and follow the on-screen instructions. ++ ++You can also check out existing plugins on [the plugin marketplace](https://backstage.io/plugins)! +diff --git a/yarn.lock b/yarn.lock +new file mode 100644 +index 0000000..5ad7fe2 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1 @@ ++# intentionally left empty diff --git a/diffs/0.4.21..0.5.7-next.1.diff b/diffs/0.4.21..0.5.7-next.1.diff new file mode 100644 index 00000000..d5e84794 --- /dev/null +++ b/diffs/0.4.21..0.5.7-next.1.diff @@ -0,0 +1,1662 @@ +diff --git a/.dockerignore b/.dockerignore +index 63c9c34..05edb62 100644 +--- a/.dockerignore ++++ b/.dockerignore +@@ -1,5 +1,8 @@ + .git ++.yarn/cache ++.yarn/install-state.gz + node_modules +-packages +-!packages/backend/dist ++packages/*/src ++packages/*/node_modules + plugins ++*.local.yaml +diff --git a/.eslintignore b/.eslintignore +new file mode 100644 +index 0000000..e5b1994 +--- /dev/null ++++ b/.eslintignore +@@ -0,0 +1 @@ ++playwright.config.ts +diff --git a/.gitignore b/.gitignore +index d16a8d3..fbf8139 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -17,2 +17,11 @@ node_modules/ + ++# Yarn 3 files ++.pnp.* ++.yarn/* ++!.yarn/patches ++!.yarn/plugins ++!.yarn/releases ++!.yarn/sdks ++!.yarn/versions ++ + # Node version directives +@@ -39 +48,7 @@ site + *-credentials.yaml ++ ++# vscode database functionality support files ++*.session.sql ++ ++# E2E test reports ++e2e-test-report/ +diff --git a/app-config.production.yaml b/app-config.production.yaml +index 5e36c23..8f0751c 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -1,3 +1,3 @@ + app: +- # Should be the same as backend.baseUrl when using the `app-backend` plugin ++ # Should be the same as backend.baseUrl when using the `app-backend` plugin. + baseUrl: http://localhost:7007 +@@ -5,4 +5,31 @@ app: + backend: ++ # Note that the baseUrl should be the URL that the browser and other clients ++ # should use when communicating with the backend, i.e. it needs to be ++ # reachable not just from within the backend host, but from all of your ++ # callers. When its value is "http://localhost:7007", it's strictly private ++ # and can't be reached by others. + baseUrl: http://localhost:7007 +- listen: +- port: 7007 ++ # The listener can also be expressed as a single : string. In this case we bind to ++ # all interfaces, the most permissive setting. The right value depends on your specific deployment. ++ listen: ':7007' ++ ++ # config options: https://node-postgres.com/api/client ++ database: ++ client: pg ++ connection: ++ host: ${POSTGRES_HOST} ++ port: ${POSTGRES_PORT} ++ user: ${POSTGRES_USER} ++ password: ${POSTGRES_PASSWORD} ++ # https://node-postgres.com/features/ssl ++ # you can set the sslmode configuration option via the `PGSSLMODE` environment variable ++ # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require) ++ # ssl: ++ # ca: # if you have a CA file and want to verify it you can uncomment this section ++ # $file: /ca/server.crt ++ ++catalog: ++ # Overrides the default list locations from app-config.yaml as these contain example data. ++ # See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details ++ # on how to get entities into the catalog. ++ locations: [] +diff --git a/app-config.yaml b/app-config.yaml +index d45e354..08d2abf 100644 +--- a/app-config.yaml ++++ b/app-config.yaml +@@ -9,3 +9,3 @@ backend: + # Used for enabling authentication, secret is shared by all backend plugins +- # See https://backstage.io/docs/tutorials/backend-to-backend-auth for ++ # See https://backstage.io/docs/auth/service-to-service-auth for + # information on the format +@@ -17,2 +17,4 @@ backend: + port: 7007 ++ # Uncomment the following host directive to bind to specific interfaces ++ # host: 127.0.0.1 + csp: +@@ -23,9 +25,9 @@ backend: + origin: http://localhost:3000 +- methods: [GET, POST, PUT, DELETE] ++ methods: [GET, HEAD, PATCH, POST, PUT, DELETE] + credentials: true ++ # This is for local development only, it is not recommended to use this in production ++ # The production database configuration is stored in app-config.production.yaml + database: +- client: sqlite3 ++ client: better-sqlite3 + connection: ':memory:' +- cache: +- store: memory + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir +@@ -35,2 +37,4 @@ integrations: + - host: github.com ++ # This is a Personal Access Token or PAT from GitHub. You can find out how to generate this token, and more information ++ # about setting up the GitHub integration here: https://backstage.io/docs/getting-started/configuration#setting-up-a-github-integration + token: ${GITHUB_TOKEN} +@@ -42,5 +46,8 @@ integrations: + proxy: +- '/test': +- target: 'https://example.com' +- changeOrigin: true ++ ### Example for how to add a proxy endpoint for the frontend. ++ ### A typical reason to do this is to handle HTTPS and CORS for internal services. ++ # endpoints: ++ # '/test': ++ # target: 'https://example.com' ++ # changeOrigin: true + +@@ -69,40 +76,28 @@ catalog: + rules: +- - allow: [Component, System, API, Group, User, Resource, Location] ++ - allow: [Component, System, API, Resource, Location] + locations: +- # Backstage example components +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-components.yaml ++ # Local example data, file locations are relative to the backend process, typically `packages/backend` ++ - type: file ++ target: ../../examples/entities.yaml + +- # Backstage example systems +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-systems.yaml +- +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml +- +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml +- +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.yaml +- +- # Backstage example templates +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/react-ssr-template/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/springboot-grpc-template/template.yaml ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml + rules: + - allow: [Template] +- - type: url +- target: https://github.com/spotify/cookiecutter-golang/blob/master/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/docs-template/template.yaml ++ ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml + rules: +- - allow: [Template] ++ - allow: [User, Group] ++ ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml ++ ++ ## Uncomment these lines to add an example org ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme-corp.yaml ++ # rules: ++ # - allow: [User, Group] +diff --git a/backstage.json b/backstage.json +index bc89f03..707af29 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "0.4.21" ++ "version": "1.20.0-next.1" + } +diff --git a/examples/entities.yaml b/examples/entities.yaml +new file mode 100644 +index 0000000..447e8b1 +--- /dev/null ++++ b/examples/entities.yaml +@@ -0,0 +1,41 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system ++apiVersion: backstage.io/v1alpha1 ++kind: System ++metadata: ++ name: examples ++spec: ++ owner: guests ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: example-website ++spec: ++ type: website ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ providesApis: [example-grpc-api] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api ++apiVersion: backstage.io/v1alpha1 ++kind: API ++metadata: ++ name: example-grpc-api ++spec: ++ type: grpc ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ definition: | ++ syntax = "proto3"; ++ ++ service Exampler { ++ rpc Example (ExampleMessage) returns (ExampleMessage) {}; ++ } ++ ++ message ExampleMessage { ++ string example = 1; ++ }; +diff --git a/examples/org.yaml b/examples/org.yaml +new file mode 100644 +index 0000000..a10e81f +--- /dev/null ++++ b/examples/org.yaml +@@ -0,0 +1,17 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user ++apiVersion: backstage.io/v1alpha1 ++kind: User ++metadata: ++ name: guest ++spec: ++ memberOf: [guests] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group ++apiVersion: backstage.io/v1alpha1 ++kind: Group ++metadata: ++ name: guests ++spec: ++ type: team ++ children: [] +diff --git a/examples/template/content/catalog-info.yaml b/examples/template/content/catalog-info.yaml +new file mode 100644 +index 0000000..d4ccca4 +--- /dev/null ++++ b/examples/template/content/catalog-info.yaml +@@ -0,0 +1,8 @@ ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: ${{ values.name | dump }} ++spec: ++ type: service ++ owner: user:guest ++ lifecycle: experimental +diff --git a/examples/template/content/index.js b/examples/template/content/index.js +new file mode 100644 +index 0000000..071ce5a +--- /dev/null ++++ b/examples/template/content/index.js +@@ -0,0 +1 @@ ++console.log('Hello from ${{ values.name }}!'); +diff --git a/examples/template/content/package.json b/examples/template/content/package.json +new file mode 100644 +index 0000000..86f968a +--- /dev/null ++++ b/examples/template/content/package.json +@@ -0,0 +1,5 @@ ++{ ++ "name": "${{ values.name }}", ++ "private": true, ++ "dependencies": {} ++} +diff --git a/examples/template/template.yaml b/examples/template/template.yaml +new file mode 100644 +index 0000000..33f262b +--- /dev/null ++++ b/examples/template/template.yaml +@@ -0,0 +1,74 @@ ++apiVersion: scaffolder.backstage.io/v1beta3 ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template ++kind: Template ++metadata: ++ name: example-nodejs-template ++ title: Example Node.js Template ++ description: An example template for the scaffolder that creates a simple Node.js service ++spec: ++ owner: user:guest ++ type: service ++ ++ # These parameters are used to generate the input form in the frontend, and are ++ # used to gather input data for the execution of the template. ++ parameters: ++ - title: Fill in some steps ++ required: ++ - name ++ properties: ++ name: ++ title: Name ++ type: string ++ description: Unique name of the component ++ ui:autofocus: true ++ ui:options: ++ rows: 5 ++ - title: Choose a location ++ required: ++ - repoUrl ++ properties: ++ repoUrl: ++ title: Repository Location ++ type: string ++ ui:field: RepoUrlPicker ++ ui:options: ++ allowedHosts: ++ - github.com ++ ++ # These steps are executed in the scaffolder backend, using data that we gathered ++ # via the parameters above. ++ steps: ++ # Each step executes an action, in this case one templates files into the working directory. ++ - id: fetch-base ++ name: Fetch Base ++ action: fetch:template ++ input: ++ url: ./content ++ values: ++ name: ${{ parameters.name }} ++ ++ # This step publishes the contents of the working directory to GitHub. ++ - id: publish ++ name: Publish ++ action: publish:github ++ input: ++ allowedHosts: ['github.com'] ++ description: This is ${{ parameters.name }} ++ repoUrl: ${{ parameters.repoUrl }} ++ ++ # The final step is to register our new component in the catalog. ++ - id: register ++ name: Register ++ action: catalog:register ++ input: ++ repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} ++ catalogInfoPath: '/catalog-info.yaml' ++ ++ # Outputs are displayed to the user after a successful execution of the template. ++ output: ++ links: ++ - title: Repository ++ url: ${{ steps['publish'].output.remoteUrl }} ++ - title: Open in catalog ++ icon: catalog ++ entityRef: ${{ steps['register'].output.entityRef }} +diff --git a/lerna.json b/lerna.json +index 322929d..529a62f 100644 +--- a/lerna.json ++++ b/lerna.json +@@ -3,4 +3,4 @@ + "npmClient": "yarn", +- "useWorkspaces": true, +- "version": "0.1.0" ++ "version": "0.1.0", ++ "$schema": "node_modules/lerna/schemas/lerna-schema.json" + } +diff --git a/package.json b/package.json +index 7818580..2364da8 100644 +--- a/package.json ++++ b/package.json +@@ -5,3 +5,3 @@ + "engines": { +- "node": "14 || 16" ++ "node": "18 || 20" + }, +@@ -11,3 +11,4 @@ + "start-backend": "yarn workspace backend start", +- "build": "lerna run build", ++ "build:backend": "yarn workspace backend build", ++ "build:all": "backstage-cli repo build --all", + "build-image": "yarn workspace backend build-image", +@@ -15,11 +16,11 @@ + "tsc:full": "tsc --skipLibCheck false --incremental false", +- "clean": "backstage-cli clean && lerna run clean", +- "diff": "lerna run diff --", +- "test": "backstage-cli test", +- "test:all": "lerna run test -- --coverage", +- "lint": "lerna run lint --since origin/master --", +- "lint:all": "lerna run lint --", ++ "clean": "backstage-cli repo clean", ++ "test": "backstage-cli repo test", ++ "test:all": "backstage-cli repo test --coverage", ++ "test:e2e": "playwright test", ++ "fix": "backstage-cli repo fix", ++ "lint": "backstage-cli repo lint --since origin/master", ++ "lint:all": "backstage-cli repo lint", + "prettier:check": "prettier --check .", +- "create-plugin": "backstage-cli create-plugin --scope internal", +- "remove-plugin": "backstage-cli remove-plugin" ++ "new": "backstage-cli new --scope internal" + }, +@@ -32,8 +33,15 @@ + "devDependencies": { +- "@backstage/cli": "^0.14.1", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/e2e-test-utils": "^0.1.0", ++ "@playwright/test": "^1.32.3", + "@spotify/prettier-config": "^12.0.0", +- "concurrently": "^6.0.0", +- "lerna": "^4.0.0", ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^9.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.5.4" ++ "typescript": "~5.2.0" ++ }, ++ "resolutions": { ++ "@types/react": "^17", ++ "@types/react-dom": "^17" + }, +diff --git a/packages/README.md b/packages/README.md +new file mode 100644 +index 0000000..6327fa0 +--- /dev/null ++++ b/packages/README.md +@@ -0,0 +1,9 @@ ++# The Packages Folder ++ ++This is where your own applications and centrally managed libraries live, each ++in a separate folder of its own. ++ ++From the start there's an `app` folder (for the frontend) and a `backend` folder ++(for the Node backend), but you can also add more modules in here that house ++your core additions and adaptations, such as themes, common React component ++libraries, utilities, and similar. +diff --git a/packages/app/.eslintignore b/packages/app/.eslintignore +new file mode 100644 +index 0000000..a48cf0d +--- /dev/null ++++ b/packages/app/.eslintignore +@@ -0,0 +1 @@ ++public +diff --git a/packages/app/.eslintrc.js b/packages/app/.eslintrc.js +index 13573ef..e2a53a6 100644 +--- a/packages/app/.eslintrc.js ++++ b/packages/app/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/app/cypress.json b/packages/app/cypress.json +deleted file mode 100644 +index 5de7ebf..0000000 +--- a/packages/app/cypress.json ++++ /dev/null +@@ -1,5 +0,0 @@ +-{ +- "baseUrl": "http://localhost:3001", +- "fixturesFolder": false, +- "pluginsFile": false +-} +diff --git a/packages/app/cypress/.eslintrc.json b/packages/app/cypress/.eslintrc.json +deleted file mode 100644 +index 2b3a458..0000000 +--- a/packages/app/cypress/.eslintrc.json ++++ /dev/null +@@ -1,21 +0,0 @@ +-{ +- "plugins": ["cypress"], +- "extends": ["plugin:cypress/recommended"], +- "rules": { +- "jest/expect-expect": [ +- "error", +- { +- "assertFunctionNames": ["expect", "cy.contains"] +- } +- ], +- "import/no-extraneous-dependencies": [ +- "error", +- { +- "devDependencies": true, +- "optionalDependencies": true, +- "peerDependencies": true, +- "bundledDependencies": true +- } +- ] +- } +-} +diff --git a/packages/app/cypress/integration/app.js b/packages/app/cypress/integration/app.js +deleted file mode 100644 +index 43fb2e3..0000000 +--- a/packages/app/cypress/integration/app.js ++++ /dev/null +@@ -1,6 +0,0 @@ +-describe('App', () => { +- it('should render the catalog', () => { +- cy.visit('/'); +- cy.contains('My Company Catalog'); +- }); +-}); +diff --git a/packages/app/e2e-tests/app.test.ts b/packages/app/e2e-tests/app.test.ts +new file mode 100644 +index 0000000..d45bc0d +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,23 @@ ++/* ++ * Copyright 2020 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { test, expect } from '@playwright/test'; ++ ++test('App should render the welcome page', async ({ page }) => { ++ await page.goto('/'); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index cfaf39c..2100405 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,25 +5,38 @@ + "bundled": true, ++ "backstage": { ++ "role": "frontend" ++ }, ++ "scripts": { ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "clean": "backstage-cli package clean", ++ "test": "backstage-cli package test", ++ "lint": "backstage-cli package lint" ++ }, + "dependencies": { +- "@backstage/app-defaults": "^0.1.9", +- "@backstage/catalog-model": "^0.11.0", +- "@backstage/cli": "^0.14.1", +- "@backstage/core-app-api": "^0.5.4", +- "@backstage/core-components": "^0.8.10", +- "@backstage/core-plugin-api": "^0.7.0", +- "@backstage/integration-react": "^0.1.23", +- "@backstage/plugin-api-docs": "^0.8.0", +- "@backstage/plugin-catalog": "^0.9.0", +- "@backstage/plugin-catalog-common": "^0.1.4", +- "@backstage/plugin-catalog-graph": "^0.2.12", +- "@backstage/plugin-catalog-import": "^0.8.3", +- "@backstage/plugin-catalog-react": "^0.7.0", +- "@backstage/plugin-github-actions": "^0.5.0", +- "@backstage/plugin-org": "^0.5.0", +- "@backstage/plugin-permission-react": "^0.3.2", +- "@backstage/plugin-scaffolder": "^0.13.0", +- "@backstage/plugin-search": "^0.7.1", +- "@backstage/plugin-tech-radar": "^0.5.7", +- "@backstage/plugin-techdocs": "^0.14.0", +- "@backstage/plugin-user-settings": "^0.3.21", +- "@backstage/theme": "^0.2.15", ++ "@backstage/app-defaults": "^1.4.5-next.1", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/core-app-api": "^1.11.1-next.0", ++ "@backstage/core-components": "^0.13.8-next.1", ++ "@backstage/core-plugin-api": "^1.8.0-next.0", ++ "@backstage/integration-react": "^1.1.21-next.1", ++ "@backstage/plugin-api-docs": "^0.9.13-next.1", ++ "@backstage/plugin-catalog": "^1.15.0-next.1", ++ "@backstage/plugin-catalog-common": "^1.0.17", ++ "@backstage/plugin-catalog-graph": "^0.2.38-next.1", ++ "@backstage/plugin-catalog-import": "^0.10.2-next.1", ++ "@backstage/plugin-catalog-react": "^1.9.0-next.1", ++ "@backstage/plugin-github-actions": "^0.6.7-next.1", ++ "@backstage/plugin-org": "^0.6.16-next.1", ++ "@backstage/plugin-permission-react": "^0.4.17-next.0", ++ "@backstage/plugin-scaffolder": "^1.16.0-next.1", ++ "@backstage/plugin-search": "^1.4.2-next.1", ++ "@backstage/plugin-search-react": "^1.7.2-next.1", ++ "@backstage/plugin-tech-radar": "^0.6.10-next.1", ++ "@backstage/plugin-techdocs": "^1.8.1-next.1", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.2-next.1", ++ "@backstage/plugin-techdocs-react": "^1.1.13-next.1", ++ "@backstage/plugin-user-settings": "^0.7.12-next.1", ++ "@backstage/theme": "^0.4.4-next.0", + "@material-ui/core": "^4.12.2", +@@ -33,29 +46,15 @@ + "react-dom": "^17.0.2", +- "react-router": "6.0.0-beta.0", +- "react-router-dom": "6.0.0-beta.0", +- "react-use": "^15.3.3" ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^0.2.6", ++ "@backstage/test-utils": "^1.4.5-next.0", ++ "@playwright/test": "^1.32.3", + "@testing-library/jest-dom": "^5.10.1", +- "@testing-library/react": "^10.4.1", +- "@testing-library/user-event": "^12.0.7", +- "@types/jest": "^26.0.7", +- "@types/node": "^14.14.32", ++ "@testing-library/react": "^12.1.3", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^8.0.0", + "@types/react-dom": "*", +- "cross-env": "^7.0.0", +- "cypress": "^7.3.0", +- "eslint-plugin-cypress": "^2.10.3", +- "start-server-and-test": "^1.10.11" +- }, +- "scripts": { +- "start": "backstage-cli app:serve", +- "build": "backstage-cli app:build", +- "clean": "backstage-cli clean", +- "test": "backstage-cli test", +- "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", +- "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", +- "lint": "backstage-cli lint", +- "cy:dev": "cypress open", +- "cy:run": "cypress run" ++ "cross-env": "^7.0.0" + }, +diff --git a/packages/app/public/index.html b/packages/app/public/index.html +index a936c73..fc758ee 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -10,3 +10,2 @@ + /> +- + +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 9b65186..8d62f29 100644 +--- a/packages/app/src/App.tsx ++++ b/packages/app/src/App.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { Navigate, Route } from 'react-router'; ++import { Navigate, Route } from 'react-router-dom'; + import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; +@@ -17,3 +17,2 @@ import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -22,2 +21,4 @@ import { + } from '@backstage/plugin-techdocs'; ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; + import { UserSettingsPage } from '@backstage/plugin-user-settings'; +@@ -30,6 +31,6 @@ import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; + import { createApp } from '@backstage/app-defaults'; +-import { FlatRoutes } from '@backstage/core-app-api'; ++import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; + import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; +-import { PermissionedRoute } from '@backstage/plugin-permission-react'; +-import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common'; ++import { RequirePermission } from '@backstage/plugin-permission-react'; ++import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; + +@@ -41,2 +42,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -47,2 +49,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -54,8 +57,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -67,5 +67,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -80,6 +82,9 @@ const routes = ( + /> +- } ++ element={ ++ ++ ++ ++ } + /> +@@ -93,4 +98,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -100,5 +105,3 @@ const App = () => ( + +- ++ , + ); +- +-export default App; +diff --git a/packages/app/src/components/Root/LogoFull.tsx b/packages/app/src/components/Root/LogoFull.tsx +index c7b1c84..47e3b73 100644 +--- a/packages/app/src/components/Root/LogoFull.tsx ++++ b/packages/app/src/components/Root/LogoFull.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/LogoIcon.tsx b/packages/app/src/components/Root/LogoIcon.tsx +index 073cf6e..7eae8c7 100644 +--- a/packages/app/src/components/Root/LogoIcon.tsx ++++ b/packages/app/src/components/Root/LogoIcon.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx +index 90738c4..6768b48 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,19 +1,3 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- +-import React, { useContext, PropsWithChildren } from 'react'; +-import { Link, makeStyles } from '@material-ui/core'; ++import React, { PropsWithChildren } from 'react'; ++import { makeStyles } from '@material-ui/core'; + import HomeIcon from '@material-ui/icons/Home'; +@@ -25,3 +9,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -34,3 +17,2 @@ import { + sidebarConfig, +- SidebarContext, + SidebarDivider, +@@ -41,2 +23,4 @@ import { + SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -62,3 +46,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -66,8 +50,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +diff --git a/packages/app/src/components/Root/index.ts b/packages/app/src/components/Root/index.ts +index dff706f..3528881 100644 +--- a/packages/app/src/components/Root/index.ts ++++ b/packages/app/src/components/Root/index.ts +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + export { Root } from './Root'; +diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx +index 84d0944..6722ea2 100644 +--- a/packages/app/src/components/catalog/EntityPage.tsx ++++ b/packages/app/src/components/catalog/EntityPage.tsx +@@ -1,16 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ + import React from 'react'; +@@ -42,2 +27,4 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +@@ -70,2 +57,13 @@ import { + ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); ++ + const cicdContent = ( +@@ -107,2 +105,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -169,3 +175,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -196,3 +202,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -215,3 +221,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -315,3 +321,6 @@ const systemPage = ( + +- ++ ++ ++ ++ + +diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx +index 469a230..1788dde 100644 +--- a/packages/app/src/components/search/SearchPage.tsx ++++ b/packages/app/src/components/search/SearchPage.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; ++import { makeStyles, Theme, Grid, Paper } from '@material-ui/core'; + +@@ -8,4 +8,5 @@ import { + } from '@backstage/plugin-catalog-react'; +-import { DocsResultListItem } from '@backstage/plugin-techdocs'; ++import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; + ++import { SearchType } from '@backstage/plugin-search'; + import { +@@ -14,6 +15,5 @@ import { + SearchResult, +- SearchType, +- DefaultResultListItem, ++ SearchPagination, + useSearch, +-} from '@backstage/plugin-search'; ++} from '@backstage/plugin-search-react'; + import { +@@ -111,32 +111,6 @@ const SearchPage = () => { + ++ + +- {({ results }) => ( +- +- {results.map(({ type, document }) => { +- switch (type) { +- case 'software-catalog': +- return ( +- +- ); +- case 'techdocs': +- return ( +- +- ); +- default: +- return ( +- +- ); +- } +- })} +- +- )} ++ } /> ++ } /> + +diff --git a/packages/backend/.eslintrc.js b/packages/backend/.eslintrc.js +index 16a033d..e2a53a6 100644 +--- a/packages/backend/.eslintrc.js ++++ b/packages/backend/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint.backend')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index dd35d4d..18548e9 100644 +--- a/packages/backend/Dockerfile ++++ b/packages/backend/Dockerfile +@@ -7,3 +7,3 @@ + # yarn tsc +-# yarn build ++# yarn build:backend + # +@@ -11,6 +11,29 @@ + +-FROM node:16-bullseye-slim ++FROM node:18-bookworm-slim + ++# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends python3 g++ build-essential && \ ++ yarn config set python /usr/bin/python3 ++ ++# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, ++# in which case you should also move better-sqlite3 to "devDependencies" in package.json. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends libsqlite3-dev ++ ++# From here on we use the least-privileged `node` user to run the backend. ++USER node ++ ++# This should create the app dir as `node`. ++# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. ++# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. + WORKDIR /app + ++# This switches many Node.js dependencies to production mode. ++ENV NODE_ENV production ++ + # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. +@@ -18,17 +41,12 @@ WORKDIR /app + # and along with yarn.lock and the root package.json, that's enough to run yarn install. +-COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ ++COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ + RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz + +-# install sqlite3 dependencies +-RUN apt-get update && \ +- apt-get install -y libsqlite3-dev python3 cmake g++ && \ +- rm -rf /var/lib/apt/lists/* && \ +- yarn config set python /usr/bin/python3 +- +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + + # Then copy the rest of the backend bundle, along with any other files we might want. +-COPY packages/backend/dist/bundle.tar.gz app-config.yaml ./ ++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"] +diff --git a/packages/backend/README.md b/packages/backend/README.md +index 02426ef..3607b0a 100644 +--- a/packages/backend/README.md ++++ b/packages/backend/README.md +@@ -18,4 +18,2 @@ To run the example backend, first go to the project root and run + yarn install +-yarn tsc +-yarn build + ``` +@@ -27,7 +25,2 @@ After that, go to the `packages/backend` directory and run + ```bash +-AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x \ +-AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x \ +-AUTH_OAUTH2_CLIENT_ID=x AUTH_OAUTH2_CLIENT_SECRET=x \ +-AUTH_OAUTH2_AUTH_URL=x AUTH_OAUTH2_TOKEN_URL=x \ +-LOG_LEVEL=debug \ + yarn start +@@ -35,4 +28,4 @@ yarn start + +-Substitute `x` for actual values, or leave them as dummy values just to try out +-the backend without using the auth or sentry features. ++If you want to override any configuration locally, for example adding any secrets, ++you can do so in `app-config.local.yaml`. + +@@ -45,3 +38,3 @@ locations to the backend. These are places where the backend can find some + entity descriptor data to consume and serve. For more information, see +-[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog). ++[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). + +@@ -65,2 +58,2 @@ and + - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +-- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) ++- [Backstage Documentation](https://backstage.io/docs) +diff --git a/packages/backend/package.json b/packages/backend/package.json +index e49967b..f224eaf 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,30 +6,33 @@ + "private": true, ++ "backstage": { ++ "role": "backend" ++ }, + "scripts": { +- "build": "backstage-cli backend:bundle", +- "build-image": "docker build ../.. -f Dockerfile --tag backstage", +- "start": "backstage-cli backend:dev", +- "lint": "backstage-cli lint", +- "test": "backstage-cli test", +- "clean": "backstage-cli clean", +- "migrate:create": "knex migrate:make -x ts" ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "lint": "backstage-cli package lint", ++ "test": "backstage-cli package test", ++ "clean": "backstage-cli package clean", ++ "build-image": "docker build ../.. -f Dockerfile --tag backstage" + }, + "dependencies": { ++ "@backstage/backend-common": "^0.19.9-next.1", ++ "@backstage/backend-tasks": "^0.5.12-next.1", ++ "@backstage/catalog-client": "^1.4.5", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/config": "^1.1.1", ++ "@backstage/plugin-app-backend": "^0.3.55-next.1", ++ "@backstage/plugin-auth-backend": "^0.20.0-next.1", ++ "@backstage/plugin-auth-node": "^0.4.1-next.1", ++ "@backstage/plugin-catalog-backend": "^1.15.0-next.1", ++ "@backstage/plugin-permission-common": "^0.7.9", ++ "@backstage/plugin-permission-node": "^0.7.18-next.1", ++ "@backstage/plugin-proxy-backend": "^0.4.5-next.1", ++ "@backstage/plugin-scaffolder-backend": "^1.19.0-next.1", ++ "@backstage/plugin-search-backend": "^1.4.7-next.1", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.16-next.1", ++ "@backstage/plugin-search-backend-node": "^1.2.11-next.1", ++ "@backstage/plugin-techdocs-backend": "^1.9.0-next.1", + "app": "link:../app", +- "@backstage/backend-common": "^0.11.0", +- "@backstage/backend-tasks": "^0.1.9", +- "@backstage/catalog-model": "^0.11.0", +- "@backstage/catalog-client": "^0.7.2", +- "@backstage/config": "^0.1.15", +- "@backstage/plugin-app-backend": "^0.3.27", +- "@backstage/plugin-auth-backend": "^0.11.0", +- "@backstage/plugin-catalog-backend": "^0.22.0", +- "@backstage/plugin-permission-common": "^0.5.1", +- "@backstage/plugin-permission-node": "^0.5.2", +- "@backstage/plugin-proxy-backend": "^0.2.21", +- "@backstage/plugin-scaffolder-backend": "^0.17.0", +- "@backstage/plugin-search-backend": "^0.4.5", +- "@backstage/plugin-search-backend-node": "^0.4.7", +- "@backstage/plugin-techdocs-backend": "^0.14.0", +- "@gitbeaker/node": "^34.6.0", +- "@octokit/rest": "^18.5.3", ++ "better-sqlite3": "^8.0.0", + "dockerode": "^3.3.1", +@@ -37,4 +40,3 @@ + "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "@vscode/sqlite3": "^5.0.7", ++ "pg": "^8.3.0", + "winston": "^3.2.1" +@@ -42,6 +44,7 @@ + "devDependencies": { +- "@backstage/cli": "^0.14.1", ++ "@backstage/cli": "^0.24.0-next.1", + "@types/dockerode": "^3.3.0", + "@types/express": "^4.17.6", +- "@types/express-serve-static-core": "^4.17.5" ++ "@types/express-serve-static-core": "^4.17.5", ++ "@types/luxon": "^2.0.4" + }, +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 70bc66b..04c4ff9 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -17,3 +17,3 @@ import { + DatabaseManager, +- SingleHostDiscovery, ++ HostDiscovery, + UrlReaders, +@@ -32,2 +32,3 @@ import { PluginEnvironment } from './types'; + import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; + +@@ -36,7 +37,11 @@ function makeCreateEnv(config: Config) { + const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++ const discovery = HostDiscovery.fromConfig(config); + const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++ const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); + const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); ++ const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); ++ ++ const identity = DefaultIdentityClient.create({ ++ discovery, ++ }); + const permissions = ServerPermissionClient.fromConfig(config, { +@@ -63,2 +68,3 @@ function makeCreateEnv(config: Config) { + permissions, ++ identity, + }; +@@ -106,3 +112,3 @@ module.hot?.accept(); + main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); ++ console.error('Backend failed to start up', error); + process.exit(1); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +index 14e19a1..7c37f68 100644 +--- a/packages/backend/src/plugins/app.ts ++++ b/packages/backend/src/plugins/app.ts +@@ -4,11 +4,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- database, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + return await createRouter({ +- logger, +- config, +- database, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, + appPackageName: 'app', +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +index 015c864..77eb6aa 100644 +--- a/packages/backend/src/plugins/auth.ts ++++ b/packages/backend/src/plugins/auth.ts +@@ -1,2 +1,6 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; ++import { ++ createRouter, ++ providers, ++ defaultAuthProviderFactories, ++} from '@backstage/plugin-auth-backend'; + import { Router } from 'express'; +@@ -4,15 +8,46 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +- tokenManager, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + return await createRouter({ +- logger, +- config, +- database, +- discovery, +- tokenManager, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, ++ providerFactories: { ++ ...defaultAuthProviderFactories, ++ ++ // This replaces the default GitHub auth provider with a customized one. ++ // The `signIn` option enables sign-in for this provider, using the ++ // identity resolution logic that's provided in the `resolver` callback. ++ // ++ // This particular resolver makes all users share a single "guest" identity. ++ // It should only be used for testing and trying out Backstage. ++ // ++ // If you want to use a production ready resolver you can switch to ++ // the one that is commented out below, it looks up a user entity in the ++ // catalog using the GitHub username of the authenticated user. ++ // That resolver requires you to have user entities populated in the catalog, ++ // for example using https://backstage.io/docs/integrations/github/org ++ // ++ // There are other resolvers to choose from, and you can also create ++ // your own, see the auth documentation for more details: ++ // ++ // https://backstage.io/docs/auth/identity-resolver ++ github: providers.github.create({ ++ signIn: { ++ resolver(_, ctx) { ++ const userRef = 'user:default/guest'; // Must be a full entity reference ++ return ctx.issueToken({ ++ claims: { ++ sub: userRef, // The user's own identity ++ ent: [userRef], // A list of identities that the user claims ownership through ++ }, ++ }); ++ }, ++ // resolver: providers.github.resolvers.usernameMatchingUserEntityName(), ++ }, ++ }), ++ }, + }); +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +index 506f6d9..54ec393 100644 +--- a/packages/backend/src/plugins/proxy.ts ++++ b/packages/backend/src/plugins/proxy.ts +@@ -4,8 +4,10 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ }); + } +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +index 6be2e97..a12fee2 100644 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ b/packages/backend/src/plugins/scaffolder.ts +@@ -1,5 +1,3 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; + import { CatalogClient } from '@backstage/catalog-client'; + import { createRouter } from '@backstage/plugin-scaffolder-backend'; +-import Docker from 'dockerode'; + import { Router } from 'express'; +@@ -7,20 +5,17 @@ import type { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- database, +- reader, +- discovery, +-}: PluginEnvironment): Promise { +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- const catalogClient = new CatalogClient({ discoveryApi: discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ const catalogClient = new CatalogClient({ ++ discoveryApi: env.discovery, ++ }); + + return await createRouter({ +- containerRunner, +- logger, +- config, +- database, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ reader: env.reader, + catalogClient, +- reader, ++ identity: env.identity, ++ permissions: env.permissions, + }); +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +index a0a1cc3..e9469dc 100644 +--- a/packages/backend/src/plugins/search.ts ++++ b/packages/backend/src/plugins/search.ts +@@ -7,15 +7,25 @@ import { + import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend'; ++import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend'; ++import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; ++import { Router } from 'express'; + +-export default async function createPlugin({ +- logger, +- permissions, +- discovery, +- config, +- tokenManager, +-}: PluginEnvironment) { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); ++ const searchEngine = new LunrSearchEngine({ ++ logger: env.logger, ++ }); ++ const indexBuilder = new IndexBuilder({ ++ logger: env.logger, ++ searchEngine, ++ }); ++ ++ const schedule = env.scheduler.createScheduledTaskRunner({ ++ frequency: { minutes: 10 }, ++ timeout: { minutes: 15 }, ++ // A 3 second delay gives the backend server a chance to initialize before ++ // any collators are executed, which may attempt requests against the API. ++ initialDelay: { seconds: 3 }, ++ }); + +@@ -24,6 +34,6 @@ export default async function createPlugin({ + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { +- discovery, +- tokenManager, ++ schedule, ++ factory: DefaultCatalogCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, + }), +@@ -33,7 +43,7 @@ export default async function createPlugin({ + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultTechDocsCollator.fromConfig(config, { +- discovery, +- logger, +- tokenManager, ++ schedule, ++ factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ logger: env.logger, ++ tokenManager: env.tokenManager, + }), +@@ -44,6 +54,4 @@ export default async function createPlugin({ + const { scheduler } = await indexBuilder.build(); ++ scheduler.start(); + +- // A 3 second delay gives the backend server a chance to initialize before +- // any collators are executed, which may attempt requests against the API. +- setTimeout(() => scheduler.start(), 3000); + useHotCleanup(module, () => scheduler.stop()); +@@ -53,5 +61,5 @@ export default async function createPlugin({ + types: indexBuilder.getDocumentTypes(), +- permissions, +- config, +- logger, ++ permissions: env.permissions, ++ config: env.config, ++ logger: env.logger, + }); +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +index 054c64d..be8bb0c 100644 +--- a/packages/backend/src/plugins/techdocs.ts ++++ b/packages/backend/src/plugins/techdocs.ts +@@ -11,13 +11,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +- cache, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, ++ const preparers = await Preparers.fromConfig(env.config, { ++ logger: env.logger, ++ reader: env.reader, + }); +@@ -29,4 +25,4 @@ export default async function createPlugin({ + // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, ++ const generators = await Generators.fromConfig(env.config, { ++ logger: env.logger, + containerRunner, +@@ -37,5 +33,5 @@ export default async function createPlugin({ + // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, ++ const publisher = await Publisher.fromConfig(env.config, { ++ logger: env.logger, ++ discovery: env.discovery, + }); +@@ -49,6 +45,6 @@ export default async function createPlugin({ + publisher, +- logger, +- config, +- discovery, +- cache, ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ cache: env.cache, + }); +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +index 0862b0e..9cd2c74 100644 +--- a/packages/backend/src/types.ts ++++ b/packages/backend/src/types.ts +@@ -10,3 +10,4 @@ import { + import { PluginTaskScheduler } from '@backstage/backend-tasks'; +-import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; ++import { PermissionEvaluator } from '@backstage/plugin-permission-common'; ++import { IdentityApi } from '@backstage/plugin-auth-node'; + +@@ -21,3 +22,4 @@ export type PluginEnvironment = { + scheduler: PluginTaskScheduler; +- permissions: PermissionAuthorizer; ++ permissions: PermissionEvaluator; ++ identity: IdentityApi; + }; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..37c7fb1 +--- /dev/null ++++ b/playwright.config.ts +@@ -0,0 +1,60 @@ ++/* ++ * Copyright 2023 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { defineConfig } from '@playwright/test'; ++import { generateProjects } from '@backstage/e2e-test-utils/playwright'; ++ ++/** ++ * See https://playwright.dev/docs/test-configuration. ++ */ ++export default defineConfig({ ++ timeout: 60_000, ++ ++ expect: { ++ timeout: 5_000, ++ }, ++ ++ // Run your local dev server before starting the tests ++ webServer: process.env.CI ++ ? [] ++ : [ ++ { ++ command: 'yarn start', ++ port: 3000, ++ reuseExistingServer: true, ++ timeout: 60_000, ++ }, ++ ], ++ ++ forbidOnly: !!process.env.CI, ++ ++ retries: process.env.CI ? 2 : 0, ++ ++ reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]], ++ ++ use: { ++ actionTimeout: 0, ++ baseURL: ++ process.env.PLAYWRIGHT_URL ?? ++ (process.env.CI ? 'http://localhost:7007' : 'http://localhost:3000'), ++ screenshot: 'only-on-failure', ++ trace: 'on-first-retry', ++ }, ++ ++ outputDir: 'node_modules/.cache/e2e-test-results', ++ ++ projects: generateProjects(), // Find all packages with e2e-test folders ++}); +diff --git a/plugins/README.md b/plugins/README.md +new file mode 100644 +index 0000000..d7865fd +--- /dev/null ++++ b/plugins/README.md +@@ -0,0 +1,9 @@ ++# The Plugins Folder ++ ++This is where your own plugins and their associated modules live, each in a ++separate folder of its own. ++ ++If you want to create a new plugin here, go to your project root directory, run ++the command `yarn new`, and follow the on-screen instructions. ++ ++You can also check out existing plugins on [the plugin marketplace](https://backstage.io/plugins)! +diff --git a/yarn.lock b/yarn.lock +new file mode 100644 +index 0000000..5ad7fe2 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1 @@ ++# intentionally left empty diff --git a/diffs/0.4.22..0.5.7-next.1.diff b/diffs/0.4.22..0.5.7-next.1.diff new file mode 100644 index 00000000..44fb9684 --- /dev/null +++ b/diffs/0.4.22..0.5.7-next.1.diff @@ -0,0 +1,1642 @@ +diff --git a/.dockerignore b/.dockerignore +index 63c9c34..05edb62 100644 +--- a/.dockerignore ++++ b/.dockerignore +@@ -1,5 +1,8 @@ + .git ++.yarn/cache ++.yarn/install-state.gz + node_modules +-packages +-!packages/backend/dist ++packages/*/src ++packages/*/node_modules + plugins ++*.local.yaml +diff --git a/.eslintignore b/.eslintignore +new file mode 100644 +index 0000000..e5b1994 +--- /dev/null ++++ b/.eslintignore +@@ -0,0 +1 @@ ++playwright.config.ts +diff --git a/.gitignore b/.gitignore +index d16a8d3..fbf8139 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -17,2 +17,11 @@ node_modules/ + ++# Yarn 3 files ++.pnp.* ++.yarn/* ++!.yarn/patches ++!.yarn/plugins ++!.yarn/releases ++!.yarn/sdks ++!.yarn/versions ++ + # Node version directives +@@ -39 +48,7 @@ site + *-credentials.yaml ++ ++# vscode database functionality support files ++*.session.sql ++ ++# E2E test reports ++e2e-test-report/ +diff --git a/app-config.production.yaml b/app-config.production.yaml +index 5e36c23..8f0751c 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -1,3 +1,3 @@ + app: +- # Should be the same as backend.baseUrl when using the `app-backend` plugin ++ # Should be the same as backend.baseUrl when using the `app-backend` plugin. + baseUrl: http://localhost:7007 +@@ -5,4 +5,31 @@ app: + backend: ++ # Note that the baseUrl should be the URL that the browser and other clients ++ # should use when communicating with the backend, i.e. it needs to be ++ # reachable not just from within the backend host, but from all of your ++ # callers. When its value is "http://localhost:7007", it's strictly private ++ # and can't be reached by others. + baseUrl: http://localhost:7007 +- listen: +- port: 7007 ++ # The listener can also be expressed as a single : string. In this case we bind to ++ # all interfaces, the most permissive setting. The right value depends on your specific deployment. ++ listen: ':7007' ++ ++ # config options: https://node-postgres.com/api/client ++ database: ++ client: pg ++ connection: ++ host: ${POSTGRES_HOST} ++ port: ${POSTGRES_PORT} ++ user: ${POSTGRES_USER} ++ password: ${POSTGRES_PASSWORD} ++ # https://node-postgres.com/features/ssl ++ # you can set the sslmode configuration option via the `PGSSLMODE` environment variable ++ # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require) ++ # ssl: ++ # ca: # if you have a CA file and want to verify it you can uncomment this section ++ # $file: /ca/server.crt ++ ++catalog: ++ # Overrides the default list locations from app-config.yaml as these contain example data. ++ # See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details ++ # on how to get entities into the catalog. ++ locations: [] +diff --git a/app-config.yaml b/app-config.yaml +index d45e354..08d2abf 100644 +--- a/app-config.yaml ++++ b/app-config.yaml +@@ -9,3 +9,3 @@ backend: + # Used for enabling authentication, secret is shared by all backend plugins +- # See https://backstage.io/docs/tutorials/backend-to-backend-auth for ++ # See https://backstage.io/docs/auth/service-to-service-auth for + # information on the format +@@ -17,2 +17,4 @@ backend: + port: 7007 ++ # Uncomment the following host directive to bind to specific interfaces ++ # host: 127.0.0.1 + csp: +@@ -23,9 +25,9 @@ backend: + origin: http://localhost:3000 +- methods: [GET, POST, PUT, DELETE] ++ methods: [GET, HEAD, PATCH, POST, PUT, DELETE] + credentials: true ++ # This is for local development only, it is not recommended to use this in production ++ # The production database configuration is stored in app-config.production.yaml + database: +- client: sqlite3 ++ client: better-sqlite3 + connection: ':memory:' +- cache: +- store: memory + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir +@@ -35,2 +37,4 @@ integrations: + - host: github.com ++ # This is a Personal Access Token or PAT from GitHub. You can find out how to generate this token, and more information ++ # about setting up the GitHub integration here: https://backstage.io/docs/getting-started/configuration#setting-up-a-github-integration + token: ${GITHUB_TOKEN} +@@ -42,5 +46,8 @@ integrations: + proxy: +- '/test': +- target: 'https://example.com' +- changeOrigin: true ++ ### Example for how to add a proxy endpoint for the frontend. ++ ### A typical reason to do this is to handle HTTPS and CORS for internal services. ++ # endpoints: ++ # '/test': ++ # target: 'https://example.com' ++ # changeOrigin: true + +@@ -69,40 +76,28 @@ catalog: + rules: +- - allow: [Component, System, API, Group, User, Resource, Location] ++ - allow: [Component, System, API, Resource, Location] + locations: +- # Backstage example components +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-components.yaml ++ # Local example data, file locations are relative to the backend process, typically `packages/backend` ++ - type: file ++ target: ../../examples/entities.yaml + +- # Backstage example systems +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-systems.yaml +- +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml +- +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml +- +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.yaml +- +- # Backstage example templates +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/react-ssr-template/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/springboot-grpc-template/template.yaml ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml + rules: + - allow: [Template] +- - type: url +- target: https://github.com/spotify/cookiecutter-golang/blob/master/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/docs-template/template.yaml ++ ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml + rules: +- - allow: [Template] ++ - allow: [User, Group] ++ ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml ++ ++ ## Uncomment these lines to add an example org ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme-corp.yaml ++ # rules: ++ # - allow: [User, Group] +diff --git a/backstage.json b/backstage.json +index 0656bae..707af29 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "0.4.22" ++ "version": "1.20.0-next.1" + } +diff --git a/examples/entities.yaml b/examples/entities.yaml +new file mode 100644 +index 0000000..447e8b1 +--- /dev/null ++++ b/examples/entities.yaml +@@ -0,0 +1,41 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system ++apiVersion: backstage.io/v1alpha1 ++kind: System ++metadata: ++ name: examples ++spec: ++ owner: guests ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: example-website ++spec: ++ type: website ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ providesApis: [example-grpc-api] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api ++apiVersion: backstage.io/v1alpha1 ++kind: API ++metadata: ++ name: example-grpc-api ++spec: ++ type: grpc ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ definition: | ++ syntax = "proto3"; ++ ++ service Exampler { ++ rpc Example (ExampleMessage) returns (ExampleMessage) {}; ++ } ++ ++ message ExampleMessage { ++ string example = 1; ++ }; +diff --git a/examples/org.yaml b/examples/org.yaml +new file mode 100644 +index 0000000..a10e81f +--- /dev/null ++++ b/examples/org.yaml +@@ -0,0 +1,17 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user ++apiVersion: backstage.io/v1alpha1 ++kind: User ++metadata: ++ name: guest ++spec: ++ memberOf: [guests] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group ++apiVersion: backstage.io/v1alpha1 ++kind: Group ++metadata: ++ name: guests ++spec: ++ type: team ++ children: [] +diff --git a/examples/template/content/catalog-info.yaml b/examples/template/content/catalog-info.yaml +new file mode 100644 +index 0000000..d4ccca4 +--- /dev/null ++++ b/examples/template/content/catalog-info.yaml +@@ -0,0 +1,8 @@ ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: ${{ values.name | dump }} ++spec: ++ type: service ++ owner: user:guest ++ lifecycle: experimental +diff --git a/examples/template/content/index.js b/examples/template/content/index.js +new file mode 100644 +index 0000000..071ce5a +--- /dev/null ++++ b/examples/template/content/index.js +@@ -0,0 +1 @@ ++console.log('Hello from ${{ values.name }}!'); +diff --git a/examples/template/content/package.json b/examples/template/content/package.json +new file mode 100644 +index 0000000..86f968a +--- /dev/null ++++ b/examples/template/content/package.json +@@ -0,0 +1,5 @@ ++{ ++ "name": "${{ values.name }}", ++ "private": true, ++ "dependencies": {} ++} +diff --git a/examples/template/template.yaml b/examples/template/template.yaml +new file mode 100644 +index 0000000..33f262b +--- /dev/null ++++ b/examples/template/template.yaml +@@ -0,0 +1,74 @@ ++apiVersion: scaffolder.backstage.io/v1beta3 ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template ++kind: Template ++metadata: ++ name: example-nodejs-template ++ title: Example Node.js Template ++ description: An example template for the scaffolder that creates a simple Node.js service ++spec: ++ owner: user:guest ++ type: service ++ ++ # These parameters are used to generate the input form in the frontend, and are ++ # used to gather input data for the execution of the template. ++ parameters: ++ - title: Fill in some steps ++ required: ++ - name ++ properties: ++ name: ++ title: Name ++ type: string ++ description: Unique name of the component ++ ui:autofocus: true ++ ui:options: ++ rows: 5 ++ - title: Choose a location ++ required: ++ - repoUrl ++ properties: ++ repoUrl: ++ title: Repository Location ++ type: string ++ ui:field: RepoUrlPicker ++ ui:options: ++ allowedHosts: ++ - github.com ++ ++ # These steps are executed in the scaffolder backend, using data that we gathered ++ # via the parameters above. ++ steps: ++ # Each step executes an action, in this case one templates files into the working directory. ++ - id: fetch-base ++ name: Fetch Base ++ action: fetch:template ++ input: ++ url: ./content ++ values: ++ name: ${{ parameters.name }} ++ ++ # This step publishes the contents of the working directory to GitHub. ++ - id: publish ++ name: Publish ++ action: publish:github ++ input: ++ allowedHosts: ['github.com'] ++ description: This is ${{ parameters.name }} ++ repoUrl: ${{ parameters.repoUrl }} ++ ++ # The final step is to register our new component in the catalog. ++ - id: register ++ name: Register ++ action: catalog:register ++ input: ++ repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} ++ catalogInfoPath: '/catalog-info.yaml' ++ ++ # Outputs are displayed to the user after a successful execution of the template. ++ output: ++ links: ++ - title: Repository ++ url: ${{ steps['publish'].output.remoteUrl }} ++ - title: Open in catalog ++ icon: catalog ++ entityRef: ${{ steps['register'].output.entityRef }} +diff --git a/lerna.json b/lerna.json +index 322929d..529a62f 100644 +--- a/lerna.json ++++ b/lerna.json +@@ -3,4 +3,4 @@ + "npmClient": "yarn", +- "useWorkspaces": true, +- "version": "0.1.0" ++ "version": "0.1.0", ++ "$schema": "node_modules/lerna/schemas/lerna-schema.json" + } +diff --git a/package.json b/package.json +index 22856e3..2364da8 100644 +--- a/package.json ++++ b/package.json +@@ -5,3 +5,3 @@ + "engines": { +- "node": "14 || 16" ++ "node": "18 || 20" + }, +@@ -11,3 +11,4 @@ + "start-backend": "yarn workspace backend start", +- "build": "lerna run build", ++ "build:backend": "yarn workspace backend build", ++ "build:all": "backstage-cli repo build --all", + "build-image": "yarn workspace backend build-image", +@@ -15,11 +16,11 @@ + "tsc:full": "tsc --skipLibCheck false --incremental false", +- "clean": "backstage-cli clean && lerna run clean", +- "diff": "lerna run diff --", +- "test": "backstage-cli test", +- "test:all": "lerna run test -- --coverage", +- "lint": "lerna run lint --since origin/master --", +- "lint:all": "lerna run lint --", ++ "clean": "backstage-cli repo clean", ++ "test": "backstage-cli repo test", ++ "test:all": "backstage-cli repo test --coverage", ++ "test:e2e": "playwright test", ++ "fix": "backstage-cli repo fix", ++ "lint": "backstage-cli repo lint --since origin/master", ++ "lint:all": "backstage-cli repo lint", + "prettier:check": "prettier --check .", +- "create-plugin": "backstage-cli create-plugin --scope internal", +- "remove-plugin": "backstage-cli remove-plugin" ++ "new": "backstage-cli new --scope internal" + }, +@@ -32,8 +33,15 @@ + "devDependencies": { +- "@backstage/cli": "^0.15.0", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/e2e-test-utils": "^0.1.0", ++ "@playwright/test": "^1.32.3", + "@spotify/prettier-config": "^12.0.0", +- "concurrently": "^6.0.0", +- "lerna": "^4.0.0", ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^9.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.5.4" ++ "typescript": "~5.2.0" ++ }, ++ "resolutions": { ++ "@types/react": "^17", ++ "@types/react-dom": "^17" + }, +diff --git a/packages/README.md b/packages/README.md +new file mode 100644 +index 0000000..6327fa0 +--- /dev/null ++++ b/packages/README.md +@@ -0,0 +1,9 @@ ++# The Packages Folder ++ ++This is where your own applications and centrally managed libraries live, each ++in a separate folder of its own. ++ ++From the start there's an `app` folder (for the frontend) and a `backend` folder ++(for the Node backend), but you can also add more modules in here that house ++your core additions and adaptations, such as themes, common React component ++libraries, utilities, and similar. +diff --git a/packages/app/.eslintignore b/packages/app/.eslintignore +new file mode 100644 +index 0000000..a48cf0d +--- /dev/null ++++ b/packages/app/.eslintignore +@@ -0,0 +1 @@ ++public +diff --git a/packages/app/.eslintrc.js b/packages/app/.eslintrc.js +index 13573ef..e2a53a6 100644 +--- a/packages/app/.eslintrc.js ++++ b/packages/app/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/app/cypress.json b/packages/app/cypress.json +deleted file mode 100644 +index 5de7ebf..0000000 +--- a/packages/app/cypress.json ++++ /dev/null +@@ -1,5 +0,0 @@ +-{ +- "baseUrl": "http://localhost:3001", +- "fixturesFolder": false, +- "pluginsFile": false +-} +diff --git a/packages/app/cypress/.eslintrc.json b/packages/app/cypress/.eslintrc.json +deleted file mode 100644 +index 2b3a458..0000000 +--- a/packages/app/cypress/.eslintrc.json ++++ /dev/null +@@ -1,21 +0,0 @@ +-{ +- "plugins": ["cypress"], +- "extends": ["plugin:cypress/recommended"], +- "rules": { +- "jest/expect-expect": [ +- "error", +- { +- "assertFunctionNames": ["expect", "cy.contains"] +- } +- ], +- "import/no-extraneous-dependencies": [ +- "error", +- { +- "devDependencies": true, +- "optionalDependencies": true, +- "peerDependencies": true, +- "bundledDependencies": true +- } +- ] +- } +-} +diff --git a/packages/app/cypress/integration/app.js b/packages/app/cypress/integration/app.js +deleted file mode 100644 +index 43fb2e3..0000000 +--- a/packages/app/cypress/integration/app.js ++++ /dev/null +@@ -1,6 +0,0 @@ +-describe('App', () => { +- it('should render the catalog', () => { +- cy.visit('/'); +- cy.contains('My Company Catalog'); +- }); +-}); +diff --git a/packages/app/e2e-tests/app.test.ts b/packages/app/e2e-tests/app.test.ts +new file mode 100644 +index 0000000..d45bc0d +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,23 @@ ++/* ++ * Copyright 2020 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { test, expect } from '@playwright/test'; ++ ++test('App should render the welcome page', async ({ page }) => { ++ await page.goto('/'); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 5070c85..2100405 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,25 +5,38 @@ + "bundled": true, ++ "backstage": { ++ "role": "frontend" ++ }, ++ "scripts": { ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "clean": "backstage-cli package clean", ++ "test": "backstage-cli package test", ++ "lint": "backstage-cli package lint" ++ }, + "dependencies": { +- "@backstage/app-defaults": "^0.2.0", +- "@backstage/catalog-model": "^0.12.0", +- "@backstage/cli": "^0.15.0", +- "@backstage/core-app-api": "^0.6.0", +- "@backstage/core-components": "^0.9.0", +- "@backstage/core-plugin-api": "^0.8.0", +- "@backstage/integration-react": "^0.1.24", +- "@backstage/plugin-api-docs": "^0.8.1", +- "@backstage/plugin-catalog": "^0.9.1", +- "@backstage/plugin-catalog-common": "^0.2.0", +- "@backstage/plugin-catalog-graph": "^0.2.13", +- "@backstage/plugin-catalog-import": "^0.8.4", +- "@backstage/plugin-catalog-react": "^0.8.0", +- "@backstage/plugin-github-actions": "^0.5.1", +- "@backstage/plugin-org": "^0.5.1", +- "@backstage/plugin-permission-react": "^0.3.3", +- "@backstage/plugin-scaffolder": "^0.14.0", +- "@backstage/plugin-search": "^0.7.2", +- "@backstage/plugin-tech-radar": "^0.5.8", +- "@backstage/plugin-techdocs": "^0.15.0", +- "@backstage/plugin-user-settings": "^0.4.0", +- "@backstage/theme": "^0.2.15", ++ "@backstage/app-defaults": "^1.4.5-next.1", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/core-app-api": "^1.11.1-next.0", ++ "@backstage/core-components": "^0.13.8-next.1", ++ "@backstage/core-plugin-api": "^1.8.0-next.0", ++ "@backstage/integration-react": "^1.1.21-next.1", ++ "@backstage/plugin-api-docs": "^0.9.13-next.1", ++ "@backstage/plugin-catalog": "^1.15.0-next.1", ++ "@backstage/plugin-catalog-common": "^1.0.17", ++ "@backstage/plugin-catalog-graph": "^0.2.38-next.1", ++ "@backstage/plugin-catalog-import": "^0.10.2-next.1", ++ "@backstage/plugin-catalog-react": "^1.9.0-next.1", ++ "@backstage/plugin-github-actions": "^0.6.7-next.1", ++ "@backstage/plugin-org": "^0.6.16-next.1", ++ "@backstage/plugin-permission-react": "^0.4.17-next.0", ++ "@backstage/plugin-scaffolder": "^1.16.0-next.1", ++ "@backstage/plugin-search": "^1.4.2-next.1", ++ "@backstage/plugin-search-react": "^1.7.2-next.1", ++ "@backstage/plugin-tech-radar": "^0.6.10-next.1", ++ "@backstage/plugin-techdocs": "^1.8.1-next.1", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.2-next.1", ++ "@backstage/plugin-techdocs-react": "^1.1.13-next.1", ++ "@backstage/plugin-user-settings": "^0.7.12-next.1", ++ "@backstage/theme": "^0.4.4-next.0", + "@material-ui/core": "^4.12.2", +@@ -33,29 +46,15 @@ + "react-dom": "^17.0.2", +- "react-router": "6.0.0-beta.0", +- "react-router-dom": "6.0.0-beta.0", +- "react-use": "^15.3.3" ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^0.3.0", ++ "@backstage/test-utils": "^1.4.5-next.0", ++ "@playwright/test": "^1.32.3", + "@testing-library/jest-dom": "^5.10.1", +- "@testing-library/react": "^10.4.1", +- "@testing-library/user-event": "^12.0.7", +- "@types/jest": "^26.0.7", +- "@types/node": "^14.14.32", ++ "@testing-library/react": "^12.1.3", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^8.0.0", + "@types/react-dom": "*", +- "cross-env": "^7.0.0", +- "cypress": "^7.3.0", +- "eslint-plugin-cypress": "^2.10.3", +- "start-server-and-test": "^1.10.11" +- }, +- "scripts": { +- "start": "backstage-cli app:serve", +- "build": "backstage-cli app:build", +- "clean": "backstage-cli clean", +- "test": "backstage-cli test", +- "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", +- "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", +- "lint": "backstage-cli lint", +- "cy:dev": "cypress open", +- "cy:run": "cypress run" ++ "cross-env": "^7.0.0" + }, +diff --git a/packages/app/public/index.html b/packages/app/public/index.html +index a936c73..fc758ee 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -10,3 +10,2 @@ + /> +- + +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index f4ff424..8d62f29 100644 +--- a/packages/app/src/App.tsx ++++ b/packages/app/src/App.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { Navigate, Route } from 'react-router'; ++import { Navigate, Route } from 'react-router-dom'; + import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; +@@ -21,2 +21,4 @@ import { + } from '@backstage/plugin-techdocs'; ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; + import { UserSettingsPage } from '@backstage/plugin-user-settings'; +@@ -29,5 +31,5 @@ import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; + import { createApp } from '@backstage/app-defaults'; +-import { FlatRoutes } from '@backstage/core-app-api'; ++import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; + import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; +-import { PermissionedRoute } from '@backstage/plugin-permission-react'; ++import { RequirePermission } from '@backstage/plugin-permission-react'; + import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; +@@ -40,2 +42,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -46,2 +49,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -53,8 +57,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -70,3 +71,7 @@ const routes = ( + element={} +- /> ++ > ++ ++ ++ ++ + } /> +@@ -77,6 +82,9 @@ const routes = ( + /> +- } ++ element={ ++ ++ ++ ++ } + /> +@@ -90,4 +98,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -97,5 +105,3 @@ const App = () => ( + +- ++ , + ); +- +-export default App; +diff --git a/packages/app/src/components/Root/LogoFull.tsx b/packages/app/src/components/Root/LogoFull.tsx +index c7b1c84..47e3b73 100644 +--- a/packages/app/src/components/Root/LogoFull.tsx ++++ b/packages/app/src/components/Root/LogoFull.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/LogoIcon.tsx b/packages/app/src/components/Root/LogoIcon.tsx +index 073cf6e..7eae8c7 100644 +--- a/packages/app/src/components/Root/LogoIcon.tsx ++++ b/packages/app/src/components/Root/LogoIcon.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx +index 90738c4..6768b48 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,19 +1,3 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- +-import React, { useContext, PropsWithChildren } from 'react'; +-import { Link, makeStyles } from '@material-ui/core'; ++import React, { PropsWithChildren } from 'react'; ++import { makeStyles } from '@material-ui/core'; + import HomeIcon from '@material-ui/icons/Home'; +@@ -25,3 +9,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -34,3 +17,2 @@ import { + sidebarConfig, +- SidebarContext, + SidebarDivider, +@@ -41,2 +23,4 @@ import { + SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -62,3 +46,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -66,8 +50,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +diff --git a/packages/app/src/components/Root/index.ts b/packages/app/src/components/Root/index.ts +index dff706f..3528881 100644 +--- a/packages/app/src/components/Root/index.ts ++++ b/packages/app/src/components/Root/index.ts +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + export { Root } from './Root'; +diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx +index 84d0944..6722ea2 100644 +--- a/packages/app/src/components/catalog/EntityPage.tsx ++++ b/packages/app/src/components/catalog/EntityPage.tsx +@@ -1,16 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ + import React from 'react'; +@@ -42,2 +27,4 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +@@ -70,2 +57,13 @@ import { + ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); ++ + const cicdContent = ( +@@ -107,2 +105,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -169,3 +175,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -196,3 +202,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -215,3 +221,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -315,3 +321,6 @@ const systemPage = ( + +- ++ ++ ++ ++ + +diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx +index cd4603e..1788dde 100644 +--- a/packages/app/src/components/search/SearchPage.tsx ++++ b/packages/app/src/components/search/SearchPage.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; ++import { makeStyles, Theme, Grid, Paper } from '@material-ui/core'; + +@@ -10,2 +10,3 @@ import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; + ++import { SearchType } from '@backstage/plugin-search'; + import { +@@ -14,6 +15,5 @@ import { + SearchResult, +- SearchType, +- DefaultResultListItem, ++ SearchPagination, + useSearch, +-} from '@backstage/plugin-search'; ++} from '@backstage/plugin-search-react'; + import { +@@ -111,32 +111,6 @@ const SearchPage = () => { + ++ + +- {({ results }) => ( +- +- {results.map(({ type, document }) => { +- switch (type) { +- case 'software-catalog': +- return ( +- +- ); +- case 'techdocs': +- return ( +- +- ); +- default: +- return ( +- +- ); +- } +- })} +- +- )} ++ } /> ++ } /> + +diff --git a/packages/backend/.eslintrc.js b/packages/backend/.eslintrc.js +index 16a033d..e2a53a6 100644 +--- a/packages/backend/.eslintrc.js ++++ b/packages/backend/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint.backend')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index dd35d4d..18548e9 100644 +--- a/packages/backend/Dockerfile ++++ b/packages/backend/Dockerfile +@@ -7,3 +7,3 @@ + # yarn tsc +-# yarn build ++# yarn build:backend + # +@@ -11,6 +11,29 @@ + +-FROM node:16-bullseye-slim ++FROM node:18-bookworm-slim + ++# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends python3 g++ build-essential && \ ++ yarn config set python /usr/bin/python3 ++ ++# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, ++# in which case you should also move better-sqlite3 to "devDependencies" in package.json. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends libsqlite3-dev ++ ++# From here on we use the least-privileged `node` user to run the backend. ++USER node ++ ++# This should create the app dir as `node`. ++# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. ++# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. + WORKDIR /app + ++# This switches many Node.js dependencies to production mode. ++ENV NODE_ENV production ++ + # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. +@@ -18,17 +41,12 @@ WORKDIR /app + # and along with yarn.lock and the root package.json, that's enough to run yarn install. +-COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ ++COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ + RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz + +-# install sqlite3 dependencies +-RUN apt-get update && \ +- apt-get install -y libsqlite3-dev python3 cmake g++ && \ +- rm -rf /var/lib/apt/lists/* && \ +- yarn config set python /usr/bin/python3 +- +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + + # Then copy the rest of the backend bundle, along with any other files we might want. +-COPY packages/backend/dist/bundle.tar.gz app-config.yaml ./ ++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"] +diff --git a/packages/backend/README.md b/packages/backend/README.md +index 02426ef..3607b0a 100644 +--- a/packages/backend/README.md ++++ b/packages/backend/README.md +@@ -18,4 +18,2 @@ To run the example backend, first go to the project root and run + yarn install +-yarn tsc +-yarn build + ``` +@@ -27,7 +25,2 @@ After that, go to the `packages/backend` directory and run + ```bash +-AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x \ +-AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x \ +-AUTH_OAUTH2_CLIENT_ID=x AUTH_OAUTH2_CLIENT_SECRET=x \ +-AUTH_OAUTH2_AUTH_URL=x AUTH_OAUTH2_TOKEN_URL=x \ +-LOG_LEVEL=debug \ + yarn start +@@ -35,4 +28,4 @@ yarn start + +-Substitute `x` for actual values, or leave them as dummy values just to try out +-the backend without using the auth or sentry features. ++If you want to override any configuration locally, for example adding any secrets, ++you can do so in `app-config.local.yaml`. + +@@ -45,3 +38,3 @@ locations to the backend. These are places where the backend can find some + entity descriptor data to consume and serve. For more information, see +-[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog). ++[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). + +@@ -65,2 +58,2 @@ and + - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +-- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) ++- [Backstage Documentation](https://backstage.io/docs) +diff --git a/packages/backend/package.json b/packages/backend/package.json +index eb3cecc..f224eaf 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,30 +6,33 @@ + "private": true, ++ "backstage": { ++ "role": "backend" ++ }, + "scripts": { +- "build": "backstage-cli backend:bundle", +- "build-image": "docker build ../.. -f Dockerfile --tag backstage", +- "start": "backstage-cli backend:dev", +- "lint": "backstage-cli lint", +- "test": "backstage-cli test", +- "clean": "backstage-cli clean", +- "migrate:create": "knex migrate:make -x ts" ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "lint": "backstage-cli package lint", ++ "test": "backstage-cli package test", ++ "clean": "backstage-cli package clean", ++ "build-image": "docker build ../.. -f Dockerfile --tag backstage" + }, + "dependencies": { ++ "@backstage/backend-common": "^0.19.9-next.1", ++ "@backstage/backend-tasks": "^0.5.12-next.1", ++ "@backstage/catalog-client": "^1.4.5", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/config": "^1.1.1", ++ "@backstage/plugin-app-backend": "^0.3.55-next.1", ++ "@backstage/plugin-auth-backend": "^0.20.0-next.1", ++ "@backstage/plugin-auth-node": "^0.4.1-next.1", ++ "@backstage/plugin-catalog-backend": "^1.15.0-next.1", ++ "@backstage/plugin-permission-common": "^0.7.9", ++ "@backstage/plugin-permission-node": "^0.7.18-next.1", ++ "@backstage/plugin-proxy-backend": "^0.4.5-next.1", ++ "@backstage/plugin-scaffolder-backend": "^1.19.0-next.1", ++ "@backstage/plugin-search-backend": "^1.4.7-next.1", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.16-next.1", ++ "@backstage/plugin-search-backend-node": "^1.2.11-next.1", ++ "@backstage/plugin-techdocs-backend": "^1.9.0-next.1", + "app": "link:../app", +- "@backstage/backend-common": "^0.12.0", +- "@backstage/backend-tasks": "^0.1.10", +- "@backstage/catalog-model": "^0.12.0", +- "@backstage/catalog-client": "^0.8.0", +- "@backstage/config": "^0.1.15", +- "@backstage/plugin-app-backend": "^0.3.28", +- "@backstage/plugin-auth-backend": "^0.12.0", +- "@backstage/plugin-catalog-backend": "^0.23.0", +- "@backstage/plugin-permission-common": "^0.5.2", +- "@backstage/plugin-permission-node": "^0.5.3", +- "@backstage/plugin-proxy-backend": "^0.2.22", +- "@backstage/plugin-scaffolder-backend": "^0.17.3", +- "@backstage/plugin-search-backend": "^0.4.6", +- "@backstage/plugin-search-backend-node": "^0.5.0", +- "@backstage/plugin-techdocs-backend": "^0.14.1", +- "@gitbeaker/node": "^34.6.0", +- "@octokit/rest": "^18.5.3", ++ "better-sqlite3": "^8.0.0", + "dockerode": "^3.3.1", +@@ -37,4 +40,3 @@ + "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "@vscode/sqlite3": "^5.0.7", ++ "pg": "^8.3.0", + "winston": "^3.2.1" +@@ -42,6 +44,7 @@ + "devDependencies": { +- "@backstage/cli": "^0.15.0", ++ "@backstage/cli": "^0.24.0-next.1", + "@types/dockerode": "^3.3.0", + "@types/express": "^4.17.6", +- "@types/express-serve-static-core": "^4.17.5" ++ "@types/express-serve-static-core": "^4.17.5", ++ "@types/luxon": "^2.0.4" + }, +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 70bc66b..04c4ff9 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -17,3 +17,3 @@ import { + DatabaseManager, +- SingleHostDiscovery, ++ HostDiscovery, + UrlReaders, +@@ -32,2 +32,3 @@ import { PluginEnvironment } from './types'; + import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; + +@@ -36,7 +37,11 @@ function makeCreateEnv(config: Config) { + const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++ const discovery = HostDiscovery.fromConfig(config); + const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++ const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); + const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); ++ const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); ++ ++ const identity = DefaultIdentityClient.create({ ++ discovery, ++ }); + const permissions = ServerPermissionClient.fromConfig(config, { +@@ -63,2 +68,3 @@ function makeCreateEnv(config: Config) { + permissions, ++ identity, + }; +@@ -106,3 +112,3 @@ module.hot?.accept(); + main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); ++ console.error('Backend failed to start up', error); + process.exit(1); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +index 14e19a1..7c37f68 100644 +--- a/packages/backend/src/plugins/app.ts ++++ b/packages/backend/src/plugins/app.ts +@@ -4,11 +4,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- database, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + return await createRouter({ +- logger, +- config, +- database, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, + appPackageName: 'app', +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +index 015c864..77eb6aa 100644 +--- a/packages/backend/src/plugins/auth.ts ++++ b/packages/backend/src/plugins/auth.ts +@@ -1,2 +1,6 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; ++import { ++ createRouter, ++ providers, ++ defaultAuthProviderFactories, ++} from '@backstage/plugin-auth-backend'; + import { Router } from 'express'; +@@ -4,15 +8,46 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +- tokenManager, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + return await createRouter({ +- logger, +- config, +- database, +- discovery, +- tokenManager, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, ++ providerFactories: { ++ ...defaultAuthProviderFactories, ++ ++ // This replaces the default GitHub auth provider with a customized one. ++ // The `signIn` option enables sign-in for this provider, using the ++ // identity resolution logic that's provided in the `resolver` callback. ++ // ++ // This particular resolver makes all users share a single "guest" identity. ++ // It should only be used for testing and trying out Backstage. ++ // ++ // If you want to use a production ready resolver you can switch to ++ // the one that is commented out below, it looks up a user entity in the ++ // catalog using the GitHub username of the authenticated user. ++ // That resolver requires you to have user entities populated in the catalog, ++ // for example using https://backstage.io/docs/integrations/github/org ++ // ++ // There are other resolvers to choose from, and you can also create ++ // your own, see the auth documentation for more details: ++ // ++ // https://backstage.io/docs/auth/identity-resolver ++ github: providers.github.create({ ++ signIn: { ++ resolver(_, ctx) { ++ const userRef = 'user:default/guest'; // Must be a full entity reference ++ return ctx.issueToken({ ++ claims: { ++ sub: userRef, // The user's own identity ++ ent: [userRef], // A list of identities that the user claims ownership through ++ }, ++ }); ++ }, ++ // resolver: providers.github.resolvers.usernameMatchingUserEntityName(), ++ }, ++ }), ++ }, + }); +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +index 506f6d9..54ec393 100644 +--- a/packages/backend/src/plugins/proxy.ts ++++ b/packages/backend/src/plugins/proxy.ts +@@ -4,8 +4,10 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ }); + } +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +index 6be2e97..a12fee2 100644 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ b/packages/backend/src/plugins/scaffolder.ts +@@ -1,5 +1,3 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; + import { CatalogClient } from '@backstage/catalog-client'; + import { createRouter } from '@backstage/plugin-scaffolder-backend'; +-import Docker from 'dockerode'; + import { Router } from 'express'; +@@ -7,20 +5,17 @@ import type { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- database, +- reader, +- discovery, +-}: PluginEnvironment): Promise { +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- const catalogClient = new CatalogClient({ discoveryApi: discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ const catalogClient = new CatalogClient({ ++ discoveryApi: env.discovery, ++ }); + + return await createRouter({ +- containerRunner, +- logger, +- config, +- database, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ reader: env.reader, + catalogClient, +- reader, ++ identity: env.identity, ++ permissions: env.permissions, + }); +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +index c359cb4..e9469dc 100644 +--- a/packages/backend/src/plugins/search.ts ++++ b/packages/backend/src/plugins/search.ts +@@ -9,13 +9,23 @@ import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend + import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; ++import { Router } from 'express'; + +-export default async function createPlugin({ +- logger, +- permissions, +- discovery, +- config, +- tokenManager, +-}: PluginEnvironment) { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); ++ const searchEngine = new LunrSearchEngine({ ++ logger: env.logger, ++ }); ++ const indexBuilder = new IndexBuilder({ ++ logger: env.logger, ++ searchEngine, ++ }); ++ ++ const schedule = env.scheduler.createScheduledTaskRunner({ ++ frequency: { minutes: 10 }, ++ timeout: { minutes: 15 }, ++ // A 3 second delay gives the backend server a chance to initialize before ++ // any collators are executed, which may attempt requests against the API. ++ initialDelay: { seconds: 3 }, ++ }); + +@@ -24,6 +34,6 @@ export default async function createPlugin({ + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- factory: DefaultCatalogCollatorFactory.fromConfig(config, { +- discovery, +- tokenManager, ++ schedule, ++ factory: DefaultCatalogCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, + }), +@@ -33,7 +43,7 @@ export default async function createPlugin({ + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- factory: DefaultTechDocsCollatorFactory.fromConfig(config, { +- discovery, +- logger, +- tokenManager, ++ schedule, ++ factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ logger: env.logger, ++ tokenManager: env.tokenManager, + }), +@@ -44,6 +54,4 @@ export default async function createPlugin({ + const { scheduler } = await indexBuilder.build(); ++ scheduler.start(); + +- // A 3 second delay gives the backend server a chance to initialize before +- // any collators are executed, which may attempt requests against the API. +- setTimeout(() => scheduler.start(), 3000); + useHotCleanup(module, () => scheduler.stop()); +@@ -53,5 +61,5 @@ export default async function createPlugin({ + types: indexBuilder.getDocumentTypes(), +- permissions, +- config, +- logger, ++ permissions: env.permissions, ++ config: env.config, ++ logger: env.logger, + }); +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +index 054c64d..be8bb0c 100644 +--- a/packages/backend/src/plugins/techdocs.ts ++++ b/packages/backend/src/plugins/techdocs.ts +@@ -11,13 +11,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +- cache, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, ++ const preparers = await Preparers.fromConfig(env.config, { ++ logger: env.logger, ++ reader: env.reader, + }); +@@ -29,4 +25,4 @@ export default async function createPlugin({ + // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, ++ const generators = await Generators.fromConfig(env.config, { ++ logger: env.logger, + containerRunner, +@@ -37,5 +33,5 @@ export default async function createPlugin({ + // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, ++ const publisher = await Publisher.fromConfig(env.config, { ++ logger: env.logger, ++ discovery: env.discovery, + }); +@@ -49,6 +45,6 @@ export default async function createPlugin({ + publisher, +- logger, +- config, +- discovery, +- cache, ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ cache: env.cache, + }); +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +index 0862b0e..9cd2c74 100644 +--- a/packages/backend/src/types.ts ++++ b/packages/backend/src/types.ts +@@ -10,3 +10,4 @@ import { + import { PluginTaskScheduler } from '@backstage/backend-tasks'; +-import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; ++import { PermissionEvaluator } from '@backstage/plugin-permission-common'; ++import { IdentityApi } from '@backstage/plugin-auth-node'; + +@@ -21,3 +22,4 @@ export type PluginEnvironment = { + scheduler: PluginTaskScheduler; +- permissions: PermissionAuthorizer; ++ permissions: PermissionEvaluator; ++ identity: IdentityApi; + }; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..37c7fb1 +--- /dev/null ++++ b/playwright.config.ts +@@ -0,0 +1,60 @@ ++/* ++ * Copyright 2023 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { defineConfig } from '@playwright/test'; ++import { generateProjects } from '@backstage/e2e-test-utils/playwright'; ++ ++/** ++ * See https://playwright.dev/docs/test-configuration. ++ */ ++export default defineConfig({ ++ timeout: 60_000, ++ ++ expect: { ++ timeout: 5_000, ++ }, ++ ++ // Run your local dev server before starting the tests ++ webServer: process.env.CI ++ ? [] ++ : [ ++ { ++ command: 'yarn start', ++ port: 3000, ++ reuseExistingServer: true, ++ timeout: 60_000, ++ }, ++ ], ++ ++ forbidOnly: !!process.env.CI, ++ ++ retries: process.env.CI ? 2 : 0, ++ ++ reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]], ++ ++ use: { ++ actionTimeout: 0, ++ baseURL: ++ process.env.PLAYWRIGHT_URL ?? ++ (process.env.CI ? 'http://localhost:7007' : 'http://localhost:3000'), ++ screenshot: 'only-on-failure', ++ trace: 'on-first-retry', ++ }, ++ ++ outputDir: 'node_modules/.cache/e2e-test-results', ++ ++ projects: generateProjects(), // Find all packages with e2e-test folders ++}); +diff --git a/plugins/README.md b/plugins/README.md +new file mode 100644 +index 0000000..d7865fd +--- /dev/null ++++ b/plugins/README.md +@@ -0,0 +1,9 @@ ++# The Plugins Folder ++ ++This is where your own plugins and their associated modules live, each in a ++separate folder of its own. ++ ++If you want to create a new plugin here, go to your project root directory, run ++the command `yarn new`, and follow the on-screen instructions. ++ ++You can also check out existing plugins on [the plugin marketplace](https://backstage.io/plugins)! +diff --git a/yarn.lock b/yarn.lock +new file mode 100644 +index 0000000..5ad7fe2 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1 @@ ++# intentionally left empty diff --git a/diffs/0.4.23..0.5.7-next.1.diff b/diffs/0.4.23..0.5.7-next.1.diff new file mode 100644 index 00000000..201e6c60 --- /dev/null +++ b/diffs/0.4.23..0.5.7-next.1.diff @@ -0,0 +1,1592 @@ +diff --git a/.dockerignore b/.dockerignore +index 63c9c34..05edb62 100644 +--- a/.dockerignore ++++ b/.dockerignore +@@ -1,5 +1,8 @@ + .git ++.yarn/cache ++.yarn/install-state.gz + node_modules +-packages +-!packages/backend/dist ++packages/*/src ++packages/*/node_modules + plugins ++*.local.yaml +diff --git a/.eslintignore b/.eslintignore +new file mode 100644 +index 0000000..e5b1994 +--- /dev/null ++++ b/.eslintignore +@@ -0,0 +1 @@ ++playwright.config.ts +diff --git a/.gitignore b/.gitignore +index d16a8d3..fbf8139 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -17,2 +17,11 @@ node_modules/ + ++# Yarn 3 files ++.pnp.* ++.yarn/* ++!.yarn/patches ++!.yarn/plugins ++!.yarn/releases ++!.yarn/sdks ++!.yarn/versions ++ + # Node version directives +@@ -39 +48,7 @@ site + *-credentials.yaml ++ ++# vscode database functionality support files ++*.session.sql ++ ++# E2E test reports ++e2e-test-report/ +diff --git a/app-config.production.yaml b/app-config.production.yaml +index 5e36c23..8f0751c 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -1,3 +1,3 @@ + app: +- # Should be the same as backend.baseUrl when using the `app-backend` plugin ++ # Should be the same as backend.baseUrl when using the `app-backend` plugin. + baseUrl: http://localhost:7007 +@@ -5,4 +5,31 @@ app: + backend: ++ # Note that the baseUrl should be the URL that the browser and other clients ++ # should use when communicating with the backend, i.e. it needs to be ++ # reachable not just from within the backend host, but from all of your ++ # callers. When its value is "http://localhost:7007", it's strictly private ++ # and can't be reached by others. + baseUrl: http://localhost:7007 +- listen: +- port: 7007 ++ # The listener can also be expressed as a single : string. In this case we bind to ++ # all interfaces, the most permissive setting. The right value depends on your specific deployment. ++ listen: ':7007' ++ ++ # config options: https://node-postgres.com/api/client ++ database: ++ client: pg ++ connection: ++ host: ${POSTGRES_HOST} ++ port: ${POSTGRES_PORT} ++ user: ${POSTGRES_USER} ++ password: ${POSTGRES_PASSWORD} ++ # https://node-postgres.com/features/ssl ++ # you can set the sslmode configuration option via the `PGSSLMODE` environment variable ++ # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require) ++ # ssl: ++ # ca: # if you have a CA file and want to verify it you can uncomment this section ++ # $file: /ca/server.crt ++ ++catalog: ++ # Overrides the default list locations from app-config.yaml as these contain example data. ++ # See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details ++ # on how to get entities into the catalog. ++ locations: [] +diff --git a/app-config.yaml b/app-config.yaml +index 64a661d..08d2abf 100644 +--- a/app-config.yaml ++++ b/app-config.yaml +@@ -9,3 +9,3 @@ backend: + # Used for enabling authentication, secret is shared by all backend plugins +- # See https://backstage.io/docs/tutorials/backend-to-backend-auth for ++ # See https://backstage.io/docs/auth/service-to-service-auth for + # information on the format +@@ -17,2 +17,4 @@ backend: + port: 7007 ++ # Uncomment the following host directive to bind to specific interfaces ++ # host: 127.0.0.1 + csp: +@@ -23,9 +25,9 @@ backend: + origin: http://localhost:3000 +- methods: [GET, POST, PUT, DELETE] ++ methods: [GET, HEAD, PATCH, POST, PUT, DELETE] + credentials: true ++ # This is for local development only, it is not recommended to use this in production ++ # The production database configuration is stored in app-config.production.yaml + database: +- client: sqlite3 ++ client: better-sqlite3 + connection: ':memory:' +- cache: +- store: memory + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir +@@ -35,2 +37,4 @@ integrations: + - host: github.com ++ # This is a Personal Access Token or PAT from GitHub. You can find out how to generate this token, and more information ++ # about setting up the GitHub integration here: https://backstage.io/docs/getting-started/configuration#setting-up-a-github-integration + token: ${GITHUB_TOKEN} +@@ -42,5 +46,8 @@ integrations: + proxy: +- '/test': +- target: 'https://example.com' +- changeOrigin: true ++ ### Example for how to add a proxy endpoint for the frontend. ++ ### A typical reason to do this is to handle HTTPS and CORS for internal services. ++ # endpoints: ++ # '/test': ++ # target: 'https://example.com' ++ # changeOrigin: true + +@@ -69,36 +76,28 @@ catalog: + rules: +- - allow: [Component, System, API, Group, User, Resource, Location] ++ - allow: [Component, System, API, Resource, Location] + locations: +- # Backstage example components +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-components.yaml ++ # Local example data, file locations are relative to the backend process, typically `packages/backend` ++ - type: file ++ target: ../../examples/entities.yaml + +- # Backstage example systems +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-systems.yaml +- +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml +- +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml +- +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.yaml +- +- # Backstage example templates +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/react-ssr-template/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/springboot-grpc-template/template.yaml ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml + rules: + - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/docs-template/template.yaml ++ ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml + rules: +- - allow: [Template] ++ - allow: [User, Group] ++ ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml ++ ++ ## Uncomment these lines to add an example org ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme-corp.yaml ++ # rules: ++ # - allow: [User, Group] +diff --git a/backstage.json b/backstage.json +index 3666530..707af29 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "0.71.0" ++ "version": "1.20.0-next.1" + } +diff --git a/examples/entities.yaml b/examples/entities.yaml +new file mode 100644 +index 0000000..447e8b1 +--- /dev/null ++++ b/examples/entities.yaml +@@ -0,0 +1,41 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system ++apiVersion: backstage.io/v1alpha1 ++kind: System ++metadata: ++ name: examples ++spec: ++ owner: guests ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: example-website ++spec: ++ type: website ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ providesApis: [example-grpc-api] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api ++apiVersion: backstage.io/v1alpha1 ++kind: API ++metadata: ++ name: example-grpc-api ++spec: ++ type: grpc ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ definition: | ++ syntax = "proto3"; ++ ++ service Exampler { ++ rpc Example (ExampleMessage) returns (ExampleMessage) {}; ++ } ++ ++ message ExampleMessage { ++ string example = 1; ++ }; +diff --git a/examples/org.yaml b/examples/org.yaml +new file mode 100644 +index 0000000..a10e81f +--- /dev/null ++++ b/examples/org.yaml +@@ -0,0 +1,17 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user ++apiVersion: backstage.io/v1alpha1 ++kind: User ++metadata: ++ name: guest ++spec: ++ memberOf: [guests] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group ++apiVersion: backstage.io/v1alpha1 ++kind: Group ++metadata: ++ name: guests ++spec: ++ type: team ++ children: [] +diff --git a/examples/template/content/catalog-info.yaml b/examples/template/content/catalog-info.yaml +new file mode 100644 +index 0000000..d4ccca4 +--- /dev/null ++++ b/examples/template/content/catalog-info.yaml +@@ -0,0 +1,8 @@ ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: ${{ values.name | dump }} ++spec: ++ type: service ++ owner: user:guest ++ lifecycle: experimental +diff --git a/examples/template/content/index.js b/examples/template/content/index.js +new file mode 100644 +index 0000000..071ce5a +--- /dev/null ++++ b/examples/template/content/index.js +@@ -0,0 +1 @@ ++console.log('Hello from ${{ values.name }}!'); +diff --git a/examples/template/content/package.json b/examples/template/content/package.json +new file mode 100644 +index 0000000..86f968a +--- /dev/null ++++ b/examples/template/content/package.json +@@ -0,0 +1,5 @@ ++{ ++ "name": "${{ values.name }}", ++ "private": true, ++ "dependencies": {} ++} +diff --git a/examples/template/template.yaml b/examples/template/template.yaml +new file mode 100644 +index 0000000..33f262b +--- /dev/null ++++ b/examples/template/template.yaml +@@ -0,0 +1,74 @@ ++apiVersion: scaffolder.backstage.io/v1beta3 ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template ++kind: Template ++metadata: ++ name: example-nodejs-template ++ title: Example Node.js Template ++ description: An example template for the scaffolder that creates a simple Node.js service ++spec: ++ owner: user:guest ++ type: service ++ ++ # These parameters are used to generate the input form in the frontend, and are ++ # used to gather input data for the execution of the template. ++ parameters: ++ - title: Fill in some steps ++ required: ++ - name ++ properties: ++ name: ++ title: Name ++ type: string ++ description: Unique name of the component ++ ui:autofocus: true ++ ui:options: ++ rows: 5 ++ - title: Choose a location ++ required: ++ - repoUrl ++ properties: ++ repoUrl: ++ title: Repository Location ++ type: string ++ ui:field: RepoUrlPicker ++ ui:options: ++ allowedHosts: ++ - github.com ++ ++ # These steps are executed in the scaffolder backend, using data that we gathered ++ # via the parameters above. ++ steps: ++ # Each step executes an action, in this case one templates files into the working directory. ++ - id: fetch-base ++ name: Fetch Base ++ action: fetch:template ++ input: ++ url: ./content ++ values: ++ name: ${{ parameters.name }} ++ ++ # This step publishes the contents of the working directory to GitHub. ++ - id: publish ++ name: Publish ++ action: publish:github ++ input: ++ allowedHosts: ['github.com'] ++ description: This is ${{ parameters.name }} ++ repoUrl: ${{ parameters.repoUrl }} ++ ++ # The final step is to register our new component in the catalog. ++ - id: register ++ name: Register ++ action: catalog:register ++ input: ++ repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} ++ catalogInfoPath: '/catalog-info.yaml' ++ ++ # Outputs are displayed to the user after a successful execution of the template. ++ output: ++ links: ++ - title: Repository ++ url: ${{ steps['publish'].output.remoteUrl }} ++ - title: Open in catalog ++ icon: catalog ++ entityRef: ${{ steps['register'].output.entityRef }} +diff --git a/lerna.json b/lerna.json +index 322929d..529a62f 100644 +--- a/lerna.json ++++ b/lerna.json +@@ -3,4 +3,4 @@ + "npmClient": "yarn", +- "useWorkspaces": true, +- "version": "0.1.0" ++ "version": "0.1.0", ++ "$schema": "node_modules/lerna/schemas/lerna-schema.json" + } +diff --git a/package.json b/package.json +index 625a333..2364da8 100644 +--- a/package.json ++++ b/package.json +@@ -5,3 +5,3 @@ + "engines": { +- "node": "14 || 16" ++ "node": "18 || 20" + }, +@@ -11,3 +11,4 @@ + "start-backend": "yarn workspace backend start", +- "build": "backstage-cli repo build --all", ++ "build:backend": "yarn workspace backend build", ++ "build:all": "backstage-cli repo build --all", + "build-image": "yarn workspace backend build-image", +@@ -15,6 +16,7 @@ + "tsc:full": "tsc --skipLibCheck false --incremental false", +- "clean": "backstage-cli clean && lerna run clean", +- "diff": "lerna run diff --", +- "test": "backstage-cli test", +- "test:all": "lerna run test -- --coverage", ++ "clean": "backstage-cli repo clean", ++ "test": "backstage-cli repo test", ++ "test:all": "backstage-cli repo test --coverage", ++ "test:e2e": "playwright test", ++ "fix": "backstage-cli repo fix", + "lint": "backstage-cli repo lint --since origin/master", +@@ -22,4 +24,3 @@ + "prettier:check": "prettier --check .", +- "create-plugin": "backstage-cli create-plugin --scope internal", +- "remove-plugin": "backstage-cli remove-plugin" ++ "new": "backstage-cli new --scope internal" + }, +@@ -32,8 +33,15 @@ + "devDependencies": { +- "@backstage/cli": "^0.15.2", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/e2e-test-utils": "^0.1.0", ++ "@playwright/test": "^1.32.3", + "@spotify/prettier-config": "^12.0.0", +- "concurrently": "^6.0.0", +- "lerna": "^4.0.0", ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^9.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.5.4" ++ "typescript": "~5.2.0" ++ }, ++ "resolutions": { ++ "@types/react": "^17", ++ "@types/react-dom": "^17" + }, +diff --git a/packages/README.md b/packages/README.md +new file mode 100644 +index 0000000..6327fa0 +--- /dev/null ++++ b/packages/README.md +@@ -0,0 +1,9 @@ ++# The Packages Folder ++ ++This is where your own applications and centrally managed libraries live, each ++in a separate folder of its own. ++ ++From the start there's an `app` folder (for the frontend) and a `backend` folder ++(for the Node backend), but you can also add more modules in here that house ++your core additions and adaptations, such as themes, common React component ++libraries, utilities, and similar. +diff --git a/packages/app/.eslintignore b/packages/app/.eslintignore +new file mode 100644 +index 0000000..a48cf0d +--- /dev/null ++++ b/packages/app/.eslintignore +@@ -0,0 +1 @@ ++public +diff --git a/packages/app/cypress.json b/packages/app/cypress.json +deleted file mode 100644 +index 5de7ebf..0000000 +--- a/packages/app/cypress.json ++++ /dev/null +@@ -1,5 +0,0 @@ +-{ +- "baseUrl": "http://localhost:3001", +- "fixturesFolder": false, +- "pluginsFile": false +-} +diff --git a/packages/app/cypress/.eslintrc.json b/packages/app/cypress/.eslintrc.json +deleted file mode 100644 +index 2b3a458..0000000 +--- a/packages/app/cypress/.eslintrc.json ++++ /dev/null +@@ -1,21 +0,0 @@ +-{ +- "plugins": ["cypress"], +- "extends": ["plugin:cypress/recommended"], +- "rules": { +- "jest/expect-expect": [ +- "error", +- { +- "assertFunctionNames": ["expect", "cy.contains"] +- } +- ], +- "import/no-extraneous-dependencies": [ +- "error", +- { +- "devDependencies": true, +- "optionalDependencies": true, +- "peerDependencies": true, +- "bundledDependencies": true +- } +- ] +- } +-} +diff --git a/packages/app/cypress/integration/app.js b/packages/app/cypress/integration/app.js +deleted file mode 100644 +index 43fb2e3..0000000 +--- a/packages/app/cypress/integration/app.js ++++ /dev/null +@@ -1,6 +0,0 @@ +-describe('App', () => { +- it('should render the catalog', () => { +- cy.visit('/'); +- cy.contains('My Company Catalog'); +- }); +-}); +diff --git a/packages/app/e2e-tests/app.test.ts b/packages/app/e2e-tests/app.test.ts +new file mode 100644 +index 0000000..d45bc0d +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,23 @@ ++/* ++ * Copyright 2020 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { test, expect } from '@playwright/test'; ++ ++test('App should render the welcome page', async ({ page }) => { ++ await page.goto('/'); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index bb1da11..2100405 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -8,25 +8,35 @@ + }, ++ "scripts": { ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "clean": "backstage-cli package clean", ++ "test": "backstage-cli package test", ++ "lint": "backstage-cli package lint" ++ }, + "dependencies": { +- "@backstage/app-defaults": "^0.2.1", +- "@backstage/catalog-model": "^0.13.0", +- "@backstage/cli": "^0.15.2", +- "@backstage/core-app-api": "^0.6.0", +- "@backstage/core-components": "^0.9.1", +- "@backstage/core-plugin-api": "^0.8.0", +- "@backstage/integration-react": "^0.1.25", +- "@backstage/plugin-api-docs": "^0.8.2", +- "@backstage/plugin-catalog": "^0.10.0", +- "@backstage/plugin-catalog-common": "^0.2.2", +- "@backstage/plugin-catalog-graph": "^0.2.14", +- "@backstage/plugin-catalog-import": "^0.8.5", +- "@backstage/plugin-catalog-react": "^0.9.0", +- "@backstage/plugin-github-actions": "^0.5.2", +- "@backstage/plugin-org": "^0.5.2", +- "@backstage/plugin-permission-react": "^0.3.3", +- "@backstage/plugin-scaffolder": "^0.15.0", +- "@backstage/plugin-search": "^0.7.3", +- "@backstage/plugin-tech-radar": "^0.5.9", +- "@backstage/plugin-techdocs": "^0.15.1", +- "@backstage/plugin-user-settings": "^0.4.1", +- "@backstage/theme": "^0.2.15", ++ "@backstage/app-defaults": "^1.4.5-next.1", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/core-app-api": "^1.11.1-next.0", ++ "@backstage/core-components": "^0.13.8-next.1", ++ "@backstage/core-plugin-api": "^1.8.0-next.0", ++ "@backstage/integration-react": "^1.1.21-next.1", ++ "@backstage/plugin-api-docs": "^0.9.13-next.1", ++ "@backstage/plugin-catalog": "^1.15.0-next.1", ++ "@backstage/plugin-catalog-common": "^1.0.17", ++ "@backstage/plugin-catalog-graph": "^0.2.38-next.1", ++ "@backstage/plugin-catalog-import": "^0.10.2-next.1", ++ "@backstage/plugin-catalog-react": "^1.9.0-next.1", ++ "@backstage/plugin-github-actions": "^0.6.7-next.1", ++ "@backstage/plugin-org": "^0.6.16-next.1", ++ "@backstage/plugin-permission-react": "^0.4.17-next.0", ++ "@backstage/plugin-scaffolder": "^1.16.0-next.1", ++ "@backstage/plugin-search": "^1.4.2-next.1", ++ "@backstage/plugin-search-react": "^1.7.2-next.1", ++ "@backstage/plugin-tech-radar": "^0.6.10-next.1", ++ "@backstage/plugin-techdocs": "^1.8.1-next.1", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.2-next.1", ++ "@backstage/plugin-techdocs-react": "^1.1.13-next.1", ++ "@backstage/plugin-user-settings": "^0.7.12-next.1", ++ "@backstage/theme": "^0.4.4-next.0", + "@material-ui/core": "^4.12.2", +@@ -36,29 +46,15 @@ + "react-dom": "^17.0.2", +- "react-router": "6.0.0-beta.0", +- "react-router-dom": "6.0.0-beta.0", +- "react-use": "^15.3.3" ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^0.3.0", ++ "@backstage/test-utils": "^1.4.5-next.0", ++ "@playwright/test": "^1.32.3", + "@testing-library/jest-dom": "^5.10.1", +- "@testing-library/react": "^10.4.1", +- "@testing-library/user-event": "^12.0.7", +- "@types/jest": "^26.0.7", +- "@types/node": "^14.14.32", ++ "@testing-library/react": "^12.1.3", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^8.0.0", + "@types/react-dom": "*", +- "cross-env": "^7.0.0", +- "cypress": "^7.3.0", +- "eslint-plugin-cypress": "^2.10.3", +- "start-server-and-test": "^1.10.11" +- }, +- "scripts": { +- "start": "backstage-cli package start", +- "build": "backstage-cli package build", +- "clean": "backstage-cli package clean", +- "test": "backstage-cli package test", +- "lint": "backstage-cli package lint", +- "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", +- "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", +- "cy:dev": "cypress open", +- "cy:run": "cypress run" ++ "cross-env": "^7.0.0" + }, +diff --git a/packages/app/public/index.html b/packages/app/public/index.html +index a936c73..fc758ee 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -10,3 +10,2 @@ + /> +- + +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index f4ff424..8d62f29 100644 +--- a/packages/app/src/App.tsx ++++ b/packages/app/src/App.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { Navigate, Route } from 'react-router'; ++import { Navigate, Route } from 'react-router-dom'; + import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; +@@ -21,2 +21,4 @@ import { + } from '@backstage/plugin-techdocs'; ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; + import { UserSettingsPage } from '@backstage/plugin-user-settings'; +@@ -29,5 +31,5 @@ import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; + import { createApp } from '@backstage/app-defaults'; +-import { FlatRoutes } from '@backstage/core-app-api'; ++import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; + import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; +-import { PermissionedRoute } from '@backstage/plugin-permission-react'; ++import { RequirePermission } from '@backstage/plugin-permission-react'; + import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; +@@ -40,2 +42,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -46,2 +49,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -53,8 +57,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -70,3 +71,7 @@ const routes = ( + element={} +- /> ++ > ++ ++ ++ ++ + } /> +@@ -77,6 +82,9 @@ const routes = ( + /> +- } ++ element={ ++ ++ ++ ++ } + /> +@@ -90,4 +98,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -97,5 +105,3 @@ const App = () => ( + +- ++ , + ); +- +-export default App; +diff --git a/packages/app/src/components/Root/LogoFull.tsx b/packages/app/src/components/Root/LogoFull.tsx +index c7b1c84..47e3b73 100644 +--- a/packages/app/src/components/Root/LogoFull.tsx ++++ b/packages/app/src/components/Root/LogoFull.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/LogoIcon.tsx b/packages/app/src/components/Root/LogoIcon.tsx +index 073cf6e..7eae8c7 100644 +--- a/packages/app/src/components/Root/LogoIcon.tsx ++++ b/packages/app/src/components/Root/LogoIcon.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx +index 90738c4..6768b48 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,19 +1,3 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- +-import React, { useContext, PropsWithChildren } from 'react'; +-import { Link, makeStyles } from '@material-ui/core'; ++import React, { PropsWithChildren } from 'react'; ++import { makeStyles } from '@material-ui/core'; + import HomeIcon from '@material-ui/icons/Home'; +@@ -25,3 +9,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -34,3 +17,2 @@ import { + sidebarConfig, +- SidebarContext, + SidebarDivider, +@@ -41,2 +23,4 @@ import { + SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -62,3 +46,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -66,8 +50,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +diff --git a/packages/app/src/components/Root/index.ts b/packages/app/src/components/Root/index.ts +index dff706f..3528881 100644 +--- a/packages/app/src/components/Root/index.ts ++++ b/packages/app/src/components/Root/index.ts +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + export { Root } from './Root'; +diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx +index 84d0944..6722ea2 100644 +--- a/packages/app/src/components/catalog/EntityPage.tsx ++++ b/packages/app/src/components/catalog/EntityPage.tsx +@@ -1,16 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ + import React from 'react'; +@@ -42,2 +27,4 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +@@ -70,2 +57,13 @@ import { + ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); ++ + const cicdContent = ( +@@ -107,2 +105,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -169,3 +175,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -196,3 +202,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -215,3 +221,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -315,3 +321,6 @@ const systemPage = ( + +- ++ ++ ++ ++ + +diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx +index cd4603e..1788dde 100644 +--- a/packages/app/src/components/search/SearchPage.tsx ++++ b/packages/app/src/components/search/SearchPage.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; ++import { makeStyles, Theme, Grid, Paper } from '@material-ui/core'; + +@@ -10,2 +10,3 @@ import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; + ++import { SearchType } from '@backstage/plugin-search'; + import { +@@ -14,6 +15,5 @@ import { + SearchResult, +- SearchType, +- DefaultResultListItem, ++ SearchPagination, + useSearch, +-} from '@backstage/plugin-search'; ++} from '@backstage/plugin-search-react'; + import { +@@ -111,32 +111,6 @@ const SearchPage = () => { + ++ + +- {({ results }) => ( +- +- {results.map(({ type, document }) => { +- switch (type) { +- case 'software-catalog': +- return ( +- +- ); +- case 'techdocs': +- return ( +- +- ); +- default: +- return ( +- +- ); +- } +- })} +- +- )} ++ } /> ++ } /> + +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index dd35d4d..18548e9 100644 +--- a/packages/backend/Dockerfile ++++ b/packages/backend/Dockerfile +@@ -7,3 +7,3 @@ + # yarn tsc +-# yarn build ++# yarn build:backend + # +@@ -11,6 +11,29 @@ + +-FROM node:16-bullseye-slim ++FROM node:18-bookworm-slim + ++# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends python3 g++ build-essential && \ ++ yarn config set python /usr/bin/python3 ++ ++# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, ++# in which case you should also move better-sqlite3 to "devDependencies" in package.json. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends libsqlite3-dev ++ ++# From here on we use the least-privileged `node` user to run the backend. ++USER node ++ ++# This should create the app dir as `node`. ++# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. ++# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. + WORKDIR /app + ++# This switches many Node.js dependencies to production mode. ++ENV NODE_ENV production ++ + # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. +@@ -18,17 +41,12 @@ WORKDIR /app + # and along with yarn.lock and the root package.json, that's enough to run yarn install. +-COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ ++COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ + RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz + +-# install sqlite3 dependencies +-RUN apt-get update && \ +- apt-get install -y libsqlite3-dev python3 cmake g++ && \ +- rm -rf /var/lib/apt/lists/* && \ +- yarn config set python /usr/bin/python3 +- +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + + # Then copy the rest of the backend bundle, along with any other files we might want. +-COPY packages/backend/dist/bundle.tar.gz app-config.yaml ./ ++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"] +diff --git a/packages/backend/README.md b/packages/backend/README.md +index 02426ef..3607b0a 100644 +--- a/packages/backend/README.md ++++ b/packages/backend/README.md +@@ -18,4 +18,2 @@ To run the example backend, first go to the project root and run + yarn install +-yarn tsc +-yarn build + ``` +@@ -27,7 +25,2 @@ After that, go to the `packages/backend` directory and run + ```bash +-AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x \ +-AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x \ +-AUTH_OAUTH2_CLIENT_ID=x AUTH_OAUTH2_CLIENT_SECRET=x \ +-AUTH_OAUTH2_AUTH_URL=x AUTH_OAUTH2_TOKEN_URL=x \ +-LOG_LEVEL=debug \ + yarn start +@@ -35,4 +28,4 @@ yarn start + +-Substitute `x` for actual values, or leave them as dummy values just to try out +-the backend without using the auth or sentry features. ++If you want to override any configuration locally, for example adding any secrets, ++you can do so in `app-config.local.yaml`. + +@@ -45,3 +38,3 @@ locations to the backend. These are places where the backend can find some + entity descriptor data to consume and serve. For more information, see +-[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog). ++[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). + +@@ -65,2 +58,2 @@ and + - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +-- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) ++- [Backstage Documentation](https://backstage.io/docs) +diff --git a/packages/backend/package.json b/packages/backend/package.json +index 10f5a4d..f224eaf 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -15,24 +15,24 @@ + "clean": "backstage-cli package clean", +- "build-image": "docker build ../.. -f Dockerfile --tag backstage", +- "migrate:create": "knex migrate:make -x ts" ++ "build-image": "docker build ../.. -f Dockerfile --tag backstage" + }, + "dependencies": { ++ "@backstage/backend-common": "^0.19.9-next.1", ++ "@backstage/backend-tasks": "^0.5.12-next.1", ++ "@backstage/catalog-client": "^1.4.5", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/config": "^1.1.1", ++ "@backstage/plugin-app-backend": "^0.3.55-next.1", ++ "@backstage/plugin-auth-backend": "^0.20.0-next.1", ++ "@backstage/plugin-auth-node": "^0.4.1-next.1", ++ "@backstage/plugin-catalog-backend": "^1.15.0-next.1", ++ "@backstage/plugin-permission-common": "^0.7.9", ++ "@backstage/plugin-permission-node": "^0.7.18-next.1", ++ "@backstage/plugin-proxy-backend": "^0.4.5-next.1", ++ "@backstage/plugin-scaffolder-backend": "^1.19.0-next.1", ++ "@backstage/plugin-search-backend": "^1.4.7-next.1", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.16-next.1", ++ "@backstage/plugin-search-backend-node": "^1.2.11-next.1", ++ "@backstage/plugin-techdocs-backend": "^1.9.0-next.1", + "app": "link:../app", +- "@backstage/backend-common": "^0.13.0", +- "@backstage/backend-tasks": "^0.2.0", +- "@backstage/catalog-model": "^0.13.0", +- "@backstage/catalog-client": "^0.9.0", +- "@backstage/config": "^0.1.15", +- "@backstage/plugin-app-backend": "^0.3.29", +- "@backstage/plugin-auth-backend": "^0.12.1", +- "@backstage/plugin-catalog-backend": "^0.24.0", +- "@backstage/plugin-permission-common": "^0.5.2", +- "@backstage/plugin-permission-node": "^0.5.4", +- "@backstage/plugin-proxy-backend": "^0.2.23", +- "@backstage/plugin-scaffolder-backend": "^0.18.0", +- "@backstage/plugin-search-backend": "^0.4.7", +- "@backstage/plugin-search-backend-node": "^0.5.1", +- "@backstage/plugin-techdocs-backend": "^0.14.2", +- "@gitbeaker/node": "^34.6.0", +- "@octokit/rest": "^18.5.3", ++ "better-sqlite3": "^8.0.0", + "dockerode": "^3.3.1", +@@ -40,4 +40,3 @@ + "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "@vscode/sqlite3": "^5.0.7", ++ "pg": "^8.3.0", + "winston": "^3.2.1" +@@ -45,6 +44,7 @@ + "devDependencies": { +- "@backstage/cli": "^0.15.2", ++ "@backstage/cli": "^0.24.0-next.1", + "@types/dockerode": "^3.3.0", + "@types/express": "^4.17.6", +- "@types/express-serve-static-core": "^4.17.5" ++ "@types/express-serve-static-core": "^4.17.5", ++ "@types/luxon": "^2.0.4" + }, +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 70bc66b..04c4ff9 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -17,3 +17,3 @@ import { + DatabaseManager, +- SingleHostDiscovery, ++ HostDiscovery, + UrlReaders, +@@ -32,2 +32,3 @@ import { PluginEnvironment } from './types'; + import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; + +@@ -36,7 +37,11 @@ function makeCreateEnv(config: Config) { + const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++ const discovery = HostDiscovery.fromConfig(config); + const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++ const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); + const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); ++ const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); ++ ++ const identity = DefaultIdentityClient.create({ ++ discovery, ++ }); + const permissions = ServerPermissionClient.fromConfig(config, { +@@ -63,2 +68,3 @@ function makeCreateEnv(config: Config) { + permissions, ++ identity, + }; +@@ -106,3 +112,3 @@ module.hot?.accept(); + main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); ++ console.error('Backend failed to start up', error); + process.exit(1); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +index 14e19a1..7c37f68 100644 +--- a/packages/backend/src/plugins/app.ts ++++ b/packages/backend/src/plugins/app.ts +@@ -4,11 +4,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- database, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + return await createRouter({ +- logger, +- config, +- database, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, + appPackageName: 'app', +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +index 015c864..77eb6aa 100644 +--- a/packages/backend/src/plugins/auth.ts ++++ b/packages/backend/src/plugins/auth.ts +@@ -1,2 +1,6 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; ++import { ++ createRouter, ++ providers, ++ defaultAuthProviderFactories, ++} from '@backstage/plugin-auth-backend'; + import { Router } from 'express'; +@@ -4,15 +8,46 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +- tokenManager, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + return await createRouter({ +- logger, +- config, +- database, +- discovery, +- tokenManager, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, ++ providerFactories: { ++ ...defaultAuthProviderFactories, ++ ++ // This replaces the default GitHub auth provider with a customized one. ++ // The `signIn` option enables sign-in for this provider, using the ++ // identity resolution logic that's provided in the `resolver` callback. ++ // ++ // This particular resolver makes all users share a single "guest" identity. ++ // It should only be used for testing and trying out Backstage. ++ // ++ // If you want to use a production ready resolver you can switch to ++ // the one that is commented out below, it looks up a user entity in the ++ // catalog using the GitHub username of the authenticated user. ++ // That resolver requires you to have user entities populated in the catalog, ++ // for example using https://backstage.io/docs/integrations/github/org ++ // ++ // There are other resolvers to choose from, and you can also create ++ // your own, see the auth documentation for more details: ++ // ++ // https://backstage.io/docs/auth/identity-resolver ++ github: providers.github.create({ ++ signIn: { ++ resolver(_, ctx) { ++ const userRef = 'user:default/guest'; // Must be a full entity reference ++ return ctx.issueToken({ ++ claims: { ++ sub: userRef, // The user's own identity ++ ent: [userRef], // A list of identities that the user claims ownership through ++ }, ++ }); ++ }, ++ // resolver: providers.github.resolvers.usernameMatchingUserEntityName(), ++ }, ++ }), ++ }, + }); +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +index 506f6d9..54ec393 100644 +--- a/packages/backend/src/plugins/proxy.ts ++++ b/packages/backend/src/plugins/proxy.ts +@@ -4,8 +4,10 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ }); + } +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +index a460fd8..a12fee2 100644 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ b/packages/backend/src/plugins/scaffolder.ts +@@ -5,16 +5,17 @@ import type { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- database, +- reader, +- discovery, +-}: PluginEnvironment): Promise { +- const catalogClient = new CatalogClient({ discoveryApi: discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ const catalogClient = new CatalogClient({ ++ discoveryApi: env.discovery, ++ }); ++ + return await createRouter({ +- logger, +- config, +- database, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ reader: env.reader, + catalogClient, +- reader, ++ identity: env.identity, ++ permissions: env.permissions, + }); +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +index c359cb4..e9469dc 100644 +--- a/packages/backend/src/plugins/search.ts ++++ b/packages/backend/src/plugins/search.ts +@@ -9,13 +9,23 @@ import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend + import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; ++import { Router } from 'express'; + +-export default async function createPlugin({ +- logger, +- permissions, +- discovery, +- config, +- tokenManager, +-}: PluginEnvironment) { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); ++ const searchEngine = new LunrSearchEngine({ ++ logger: env.logger, ++ }); ++ const indexBuilder = new IndexBuilder({ ++ logger: env.logger, ++ searchEngine, ++ }); ++ ++ const schedule = env.scheduler.createScheduledTaskRunner({ ++ frequency: { minutes: 10 }, ++ timeout: { minutes: 15 }, ++ // A 3 second delay gives the backend server a chance to initialize before ++ // any collators are executed, which may attempt requests against the API. ++ initialDelay: { seconds: 3 }, ++ }); + +@@ -24,6 +34,6 @@ export default async function createPlugin({ + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- factory: DefaultCatalogCollatorFactory.fromConfig(config, { +- discovery, +- tokenManager, ++ schedule, ++ factory: DefaultCatalogCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, + }), +@@ -33,7 +43,7 @@ export default async function createPlugin({ + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- factory: DefaultTechDocsCollatorFactory.fromConfig(config, { +- discovery, +- logger, +- tokenManager, ++ schedule, ++ factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ logger: env.logger, ++ tokenManager: env.tokenManager, + }), +@@ -44,6 +54,4 @@ export default async function createPlugin({ + const { scheduler } = await indexBuilder.build(); ++ scheduler.start(); + +- // A 3 second delay gives the backend server a chance to initialize before +- // any collators are executed, which may attempt requests against the API. +- setTimeout(() => scheduler.start(), 3000); + useHotCleanup(module, () => scheduler.stop()); +@@ -53,5 +61,5 @@ export default async function createPlugin({ + types: indexBuilder.getDocumentTypes(), +- permissions, +- config, +- logger, ++ permissions: env.permissions, ++ config: env.config, ++ logger: env.logger, + }); +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +index 054c64d..be8bb0c 100644 +--- a/packages/backend/src/plugins/techdocs.ts ++++ b/packages/backend/src/plugins/techdocs.ts +@@ -11,13 +11,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +- cache, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, ++ const preparers = await Preparers.fromConfig(env.config, { ++ logger: env.logger, ++ reader: env.reader, + }); +@@ -29,4 +25,4 @@ export default async function createPlugin({ + // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, ++ const generators = await Generators.fromConfig(env.config, { ++ logger: env.logger, + containerRunner, +@@ -37,5 +33,5 @@ export default async function createPlugin({ + // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, ++ const publisher = await Publisher.fromConfig(env.config, { ++ logger: env.logger, ++ discovery: env.discovery, + }); +@@ -49,6 +45,6 @@ export default async function createPlugin({ + publisher, +- logger, +- config, +- discovery, +- cache, ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ cache: env.cache, + }); +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +index 0862b0e..9cd2c74 100644 +--- a/packages/backend/src/types.ts ++++ b/packages/backend/src/types.ts +@@ -10,3 +10,4 @@ import { + import { PluginTaskScheduler } from '@backstage/backend-tasks'; +-import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; ++import { PermissionEvaluator } from '@backstage/plugin-permission-common'; ++import { IdentityApi } from '@backstage/plugin-auth-node'; + +@@ -21,3 +22,4 @@ export type PluginEnvironment = { + scheduler: PluginTaskScheduler; +- permissions: PermissionAuthorizer; ++ permissions: PermissionEvaluator; ++ identity: IdentityApi; + }; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..37c7fb1 +--- /dev/null ++++ b/playwright.config.ts +@@ -0,0 +1,60 @@ ++/* ++ * Copyright 2023 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { defineConfig } from '@playwright/test'; ++import { generateProjects } from '@backstage/e2e-test-utils/playwright'; ++ ++/** ++ * See https://playwright.dev/docs/test-configuration. ++ */ ++export default defineConfig({ ++ timeout: 60_000, ++ ++ expect: { ++ timeout: 5_000, ++ }, ++ ++ // Run your local dev server before starting the tests ++ webServer: process.env.CI ++ ? [] ++ : [ ++ { ++ command: 'yarn start', ++ port: 3000, ++ reuseExistingServer: true, ++ timeout: 60_000, ++ }, ++ ], ++ ++ forbidOnly: !!process.env.CI, ++ ++ retries: process.env.CI ? 2 : 0, ++ ++ reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]], ++ ++ use: { ++ actionTimeout: 0, ++ baseURL: ++ process.env.PLAYWRIGHT_URL ?? ++ (process.env.CI ? 'http://localhost:7007' : 'http://localhost:3000'), ++ screenshot: 'only-on-failure', ++ trace: 'on-first-retry', ++ }, ++ ++ outputDir: 'node_modules/.cache/e2e-test-results', ++ ++ projects: generateProjects(), // Find all packages with e2e-test folders ++}); +diff --git a/plugins/README.md b/plugins/README.md +new file mode 100644 +index 0000000..d7865fd +--- /dev/null ++++ b/plugins/README.md +@@ -0,0 +1,9 @@ ++# The Plugins Folder ++ ++This is where your own plugins and their associated modules live, each in a ++separate folder of its own. ++ ++If you want to create a new plugin here, go to your project root directory, run ++the command `yarn new`, and follow the on-screen instructions. ++ ++You can also check out existing plugins on [the plugin marketplace](https://backstage.io/plugins)! +diff --git a/yarn.lock b/yarn.lock +new file mode 100644 +index 0000000..5ad7fe2 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1 @@ ++# intentionally left empty diff --git a/diffs/0.4.24..0.5.7-next.1.diff b/diffs/0.4.24..0.5.7-next.1.diff new file mode 100644 index 00000000..4e6a5a6d --- /dev/null +++ b/diffs/0.4.24..0.5.7-next.1.diff @@ -0,0 +1,1405 @@ +diff --git a/.dockerignore b/.dockerignore +index 63c9c34..05edb62 100644 +--- a/.dockerignore ++++ b/.dockerignore +@@ -1,5 +1,8 @@ + .git ++.yarn/cache ++.yarn/install-state.gz + node_modules +-packages +-!packages/backend/dist ++packages/*/src ++packages/*/node_modules + plugins ++*.local.yaml +diff --git a/.eslintignore b/.eslintignore +new file mode 100644 +index 0000000..e5b1994 +--- /dev/null ++++ b/.eslintignore +@@ -0,0 +1 @@ ++playwright.config.ts +diff --git a/.gitignore b/.gitignore +index d16a8d3..fbf8139 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -17,2 +17,11 @@ node_modules/ + ++# Yarn 3 files ++.pnp.* ++.yarn/* ++!.yarn/patches ++!.yarn/plugins ++!.yarn/releases ++!.yarn/sdks ++!.yarn/versions ++ + # Node version directives +@@ -39 +48,7 @@ site + *-credentials.yaml ++ ++# vscode database functionality support files ++*.session.sql ++ ++# E2E test reports ++e2e-test-report/ +diff --git a/app-config.production.yaml b/app-config.production.yaml +index 5e36c23..8f0751c 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -1,3 +1,3 @@ + app: +- # Should be the same as backend.baseUrl when using the `app-backend` plugin ++ # Should be the same as backend.baseUrl when using the `app-backend` plugin. + baseUrl: http://localhost:7007 +@@ -5,4 +5,31 @@ app: + backend: ++ # Note that the baseUrl should be the URL that the browser and other clients ++ # should use when communicating with the backend, i.e. it needs to be ++ # reachable not just from within the backend host, but from all of your ++ # callers. When its value is "http://localhost:7007", it's strictly private ++ # and can't be reached by others. + baseUrl: http://localhost:7007 +- listen: +- port: 7007 ++ # The listener can also be expressed as a single : string. In this case we bind to ++ # all interfaces, the most permissive setting. The right value depends on your specific deployment. ++ listen: ':7007' ++ ++ # config options: https://node-postgres.com/api/client ++ database: ++ client: pg ++ connection: ++ host: ${POSTGRES_HOST} ++ port: ${POSTGRES_PORT} ++ user: ${POSTGRES_USER} ++ password: ${POSTGRES_PASSWORD} ++ # https://node-postgres.com/features/ssl ++ # you can set the sslmode configuration option via the `PGSSLMODE` environment variable ++ # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require) ++ # ssl: ++ # ca: # if you have a CA file and want to verify it you can uncomment this section ++ # $file: /ca/server.crt ++ ++catalog: ++ # Overrides the default list locations from app-config.yaml as these contain example data. ++ # See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details ++ # on how to get entities into the catalog. ++ locations: [] +diff --git a/app-config.yaml b/app-config.yaml +index e7732cf..08d2abf 100644 +--- a/app-config.yaml ++++ b/app-config.yaml +@@ -9,3 +9,3 @@ backend: + # Used for enabling authentication, secret is shared by all backend plugins +- # See https://backstage.io/docs/tutorials/backend-to-backend-auth for ++ # See https://backstage.io/docs/auth/service-to-service-auth for + # information on the format +@@ -17,2 +17,4 @@ backend: + port: 7007 ++ # Uncomment the following host directive to bind to specific interfaces ++ # host: 127.0.0.1 + csp: +@@ -23,4 +25,6 @@ backend: + origin: http://localhost:3000 +- methods: [GET, POST, PUT, DELETE] ++ methods: [GET, HEAD, PATCH, POST, PUT, DELETE] + credentials: true ++ # This is for local development only, it is not recommended to use this in production ++ # The production database configuration is stored in app-config.production.yaml + database: +@@ -28,4 +32,2 @@ backend: + connection: ':memory:' +- cache: +- store: memory + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir +@@ -35,2 +37,4 @@ integrations: + - host: github.com ++ # This is a Personal Access Token or PAT from GitHub. You can find out how to generate this token, and more information ++ # about setting up the GitHub integration here: https://backstage.io/docs/getting-started/configuration#setting-up-a-github-integration + token: ${GITHUB_TOKEN} +@@ -42,5 +46,8 @@ integrations: + proxy: +- '/test': +- target: 'https://example.com' +- changeOrigin: true ++ ### Example for how to add a proxy endpoint for the frontend. ++ ### A typical reason to do this is to handle HTTPS and CORS for internal services. ++ # endpoints: ++ # '/test': ++ # target: 'https://example.com' ++ # changeOrigin: true + +@@ -69,36 +76,28 @@ catalog: + rules: +- - allow: [Component, System, API, Group, User, Resource, Location] ++ - allow: [Component, System, API, Resource, Location] + locations: +- # Backstage example components +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-components.yaml ++ # Local example data, file locations are relative to the backend process, typically `packages/backend` ++ - type: file ++ target: ../../examples/entities.yaml + +- # Backstage example systems +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-systems.yaml +- +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml +- +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml +- +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.yaml +- +- # Backstage example templates +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/react-ssr-template/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/springboot-grpc-template/template.yaml ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml + rules: + - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/docs-template/template.yaml ++ ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml + rules: +- - allow: [Template] ++ - allow: [User, Group] ++ ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml ++ ++ ## Uncomment these lines to add an example org ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme-corp.yaml ++ # rules: ++ # - allow: [User, Group] +diff --git a/backstage.json b/backstage.json +index 1587a66..707af29 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "1.0.0" ++ "version": "1.20.0-next.1" + } +diff --git a/examples/entities.yaml b/examples/entities.yaml +new file mode 100644 +index 0000000..447e8b1 +--- /dev/null ++++ b/examples/entities.yaml +@@ -0,0 +1,41 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system ++apiVersion: backstage.io/v1alpha1 ++kind: System ++metadata: ++ name: examples ++spec: ++ owner: guests ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: example-website ++spec: ++ type: website ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ providesApis: [example-grpc-api] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api ++apiVersion: backstage.io/v1alpha1 ++kind: API ++metadata: ++ name: example-grpc-api ++spec: ++ type: grpc ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ definition: | ++ syntax = "proto3"; ++ ++ service Exampler { ++ rpc Example (ExampleMessage) returns (ExampleMessage) {}; ++ } ++ ++ message ExampleMessage { ++ string example = 1; ++ }; +diff --git a/examples/org.yaml b/examples/org.yaml +new file mode 100644 +index 0000000..a10e81f +--- /dev/null ++++ b/examples/org.yaml +@@ -0,0 +1,17 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user ++apiVersion: backstage.io/v1alpha1 ++kind: User ++metadata: ++ name: guest ++spec: ++ memberOf: [guests] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group ++apiVersion: backstage.io/v1alpha1 ++kind: Group ++metadata: ++ name: guests ++spec: ++ type: team ++ children: [] +diff --git a/examples/template/content/catalog-info.yaml b/examples/template/content/catalog-info.yaml +new file mode 100644 +index 0000000..d4ccca4 +--- /dev/null ++++ b/examples/template/content/catalog-info.yaml +@@ -0,0 +1,8 @@ ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: ${{ values.name | dump }} ++spec: ++ type: service ++ owner: user:guest ++ lifecycle: experimental +diff --git a/examples/template/content/index.js b/examples/template/content/index.js +new file mode 100644 +index 0000000..071ce5a +--- /dev/null ++++ b/examples/template/content/index.js +@@ -0,0 +1 @@ ++console.log('Hello from ${{ values.name }}!'); +diff --git a/examples/template/content/package.json b/examples/template/content/package.json +new file mode 100644 +index 0000000..86f968a +--- /dev/null ++++ b/examples/template/content/package.json +@@ -0,0 +1,5 @@ ++{ ++ "name": "${{ values.name }}", ++ "private": true, ++ "dependencies": {} ++} +diff --git a/examples/template/template.yaml b/examples/template/template.yaml +new file mode 100644 +index 0000000..33f262b +--- /dev/null ++++ b/examples/template/template.yaml +@@ -0,0 +1,74 @@ ++apiVersion: scaffolder.backstage.io/v1beta3 ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template ++kind: Template ++metadata: ++ name: example-nodejs-template ++ title: Example Node.js Template ++ description: An example template for the scaffolder that creates a simple Node.js service ++spec: ++ owner: user:guest ++ type: service ++ ++ # These parameters are used to generate the input form in the frontend, and are ++ # used to gather input data for the execution of the template. ++ parameters: ++ - title: Fill in some steps ++ required: ++ - name ++ properties: ++ name: ++ title: Name ++ type: string ++ description: Unique name of the component ++ ui:autofocus: true ++ ui:options: ++ rows: 5 ++ - title: Choose a location ++ required: ++ - repoUrl ++ properties: ++ repoUrl: ++ title: Repository Location ++ type: string ++ ui:field: RepoUrlPicker ++ ui:options: ++ allowedHosts: ++ - github.com ++ ++ # These steps are executed in the scaffolder backend, using data that we gathered ++ # via the parameters above. ++ steps: ++ # Each step executes an action, in this case one templates files into the working directory. ++ - id: fetch-base ++ name: Fetch Base ++ action: fetch:template ++ input: ++ url: ./content ++ values: ++ name: ${{ parameters.name }} ++ ++ # This step publishes the contents of the working directory to GitHub. ++ - id: publish ++ name: Publish ++ action: publish:github ++ input: ++ allowedHosts: ['github.com'] ++ description: This is ${{ parameters.name }} ++ repoUrl: ${{ parameters.repoUrl }} ++ ++ # The final step is to register our new component in the catalog. ++ - id: register ++ name: Register ++ action: catalog:register ++ input: ++ repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} ++ catalogInfoPath: '/catalog-info.yaml' ++ ++ # Outputs are displayed to the user after a successful execution of the template. ++ output: ++ links: ++ - title: Repository ++ url: ${{ steps['publish'].output.remoteUrl }} ++ - title: Open in catalog ++ icon: catalog ++ entityRef: ${{ steps['register'].output.entityRef }} +diff --git a/lerna.json b/lerna.json +index 322929d..529a62f 100644 +--- a/lerna.json ++++ b/lerna.json +@@ -3,4 +3,4 @@ + "npmClient": "yarn", +- "useWorkspaces": true, +- "version": "0.1.0" ++ "version": "0.1.0", ++ "$schema": "node_modules/lerna/schemas/lerna-schema.json" + } +diff --git a/package.json b/package.json +index b5fadf8..2364da8 100644 +--- a/package.json ++++ b/package.json +@@ -5,3 +5,3 @@ + "engines": { +- "node": "14 || 16" ++ "node": "18 || 20" + }, +@@ -11,3 +11,4 @@ + "start-backend": "yarn workspace backend start", +- "build": "backstage-cli repo build --all", ++ "build:backend": "yarn workspace backend build", ++ "build:all": "backstage-cli repo build --all", + "build-image": "yarn workspace backend build-image", +@@ -15,6 +16,7 @@ + "tsc:full": "tsc --skipLibCheck false --incremental false", +- "clean": "backstage-cli clean && lerna run clean", +- "diff": "lerna run diff --", +- "test": "backstage-cli test", +- "test:all": "lerna run test -- --coverage", ++ "clean": "backstage-cli repo clean", ++ "test": "backstage-cli repo test", ++ "test:all": "backstage-cli repo test --coverage", ++ "test:e2e": "playwright test", ++ "fix": "backstage-cli repo fix", + "lint": "backstage-cli repo lint --since origin/master", +@@ -22,4 +24,3 @@ + "prettier:check": "prettier --check .", +- "create-plugin": "backstage-cli create-plugin --scope internal", +- "remove-plugin": "backstage-cli remove-plugin" ++ "new": "backstage-cli new --scope internal" + }, +@@ -32,8 +33,15 @@ + "devDependencies": { +- "@backstage/cli": "^0.16.0", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/e2e-test-utils": "^0.1.0", ++ "@playwright/test": "^1.32.3", + "@spotify/prettier-config": "^12.0.0", +- "concurrently": "^6.0.0", +- "lerna": "^4.0.0", ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^9.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.5.4" ++ "typescript": "~5.2.0" ++ }, ++ "resolutions": { ++ "@types/react": "^17", ++ "@types/react-dom": "^17" + }, +diff --git a/packages/README.md b/packages/README.md +new file mode 100644 +index 0000000..6327fa0 +--- /dev/null ++++ b/packages/README.md +@@ -0,0 +1,9 @@ ++# The Packages Folder ++ ++This is where your own applications and centrally managed libraries live, each ++in a separate folder of its own. ++ ++From the start there's an `app` folder (for the frontend) and a `backend` folder ++(for the Node backend), but you can also add more modules in here that house ++your core additions and adaptations, such as themes, common React component ++libraries, utilities, and similar. +diff --git a/packages/app/.eslintignore b/packages/app/.eslintignore +new file mode 100644 +index 0000000..a48cf0d +--- /dev/null ++++ b/packages/app/.eslintignore +@@ -0,0 +1 @@ ++public +diff --git a/packages/app/cypress.json b/packages/app/cypress.json +deleted file mode 100644 +index 5de7ebf..0000000 +--- a/packages/app/cypress.json ++++ /dev/null +@@ -1,5 +0,0 @@ +-{ +- "baseUrl": "http://localhost:3001", +- "fixturesFolder": false, +- "pluginsFile": false +-} +diff --git a/packages/app/cypress/.eslintrc.json b/packages/app/cypress/.eslintrc.json +deleted file mode 100644 +index 2b3a458..0000000 +--- a/packages/app/cypress/.eslintrc.json ++++ /dev/null +@@ -1,21 +0,0 @@ +-{ +- "plugins": ["cypress"], +- "extends": ["plugin:cypress/recommended"], +- "rules": { +- "jest/expect-expect": [ +- "error", +- { +- "assertFunctionNames": ["expect", "cy.contains"] +- } +- ], +- "import/no-extraneous-dependencies": [ +- "error", +- { +- "devDependencies": true, +- "optionalDependencies": true, +- "peerDependencies": true, +- "bundledDependencies": true +- } +- ] +- } +-} +diff --git a/packages/app/cypress/integration/app.js b/packages/app/cypress/integration/app.js +deleted file mode 100644 +index 43fb2e3..0000000 +--- a/packages/app/cypress/integration/app.js ++++ /dev/null +@@ -1,6 +0,0 @@ +-describe('App', () => { +- it('should render the catalog', () => { +- cy.visit('/'); +- cy.contains('My Company Catalog'); +- }); +-}); +diff --git a/packages/app/e2e-tests/app.test.ts b/packages/app/e2e-tests/app.test.ts +new file mode 100644 +index 0000000..d45bc0d +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,23 @@ ++/* ++ * Copyright 2020 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { test, expect } from '@playwright/test'; ++ ++test('App should render the welcome page', async ({ page }) => { ++ await page.goto('/'); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 0cfef69..2100405 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -8,25 +8,35 @@ + }, ++ "scripts": { ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "clean": "backstage-cli package clean", ++ "test": "backstage-cli package test", ++ "lint": "backstage-cli package lint" ++ }, + "dependencies": { +- "@backstage/app-defaults": "^1.0.0", +- "@backstage/catalog-model": "^1.0.0", +- "@backstage/cli": "^0.16.0", +- "@backstage/core-app-api": "^1.0.0", +- "@backstage/core-components": "^0.9.2", +- "@backstage/core-plugin-api": "^1.0.0", +- "@backstage/integration-react": "^1.0.0", +- "@backstage/plugin-api-docs": "^0.8.3", +- "@backstage/plugin-catalog": "^1.0.0", +- "@backstage/plugin-catalog-common": "^1.0.0", +- "@backstage/plugin-catalog-graph": "^0.2.15", +- "@backstage/plugin-catalog-import": "^0.8.6", +- "@backstage/plugin-catalog-react": "^1.0.0", +- "@backstage/plugin-github-actions": "^0.5.3", +- "@backstage/plugin-org": "^0.5.3", +- "@backstage/plugin-permission-react": "^0.3.4", +- "@backstage/plugin-scaffolder": "^1.0.0", +- "@backstage/plugin-search": "^0.7.4", +- "@backstage/plugin-tech-radar": "^0.5.10", +- "@backstage/plugin-techdocs": "^1.0.0", +- "@backstage/plugin-user-settings": "^0.4.2", +- "@backstage/theme": "^0.2.15", ++ "@backstage/app-defaults": "^1.4.5-next.1", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/core-app-api": "^1.11.1-next.0", ++ "@backstage/core-components": "^0.13.8-next.1", ++ "@backstage/core-plugin-api": "^1.8.0-next.0", ++ "@backstage/integration-react": "^1.1.21-next.1", ++ "@backstage/plugin-api-docs": "^0.9.13-next.1", ++ "@backstage/plugin-catalog": "^1.15.0-next.1", ++ "@backstage/plugin-catalog-common": "^1.0.17", ++ "@backstage/plugin-catalog-graph": "^0.2.38-next.1", ++ "@backstage/plugin-catalog-import": "^0.10.2-next.1", ++ "@backstage/plugin-catalog-react": "^1.9.0-next.1", ++ "@backstage/plugin-github-actions": "^0.6.7-next.1", ++ "@backstage/plugin-org": "^0.6.16-next.1", ++ "@backstage/plugin-permission-react": "^0.4.17-next.0", ++ "@backstage/plugin-scaffolder": "^1.16.0-next.1", ++ "@backstage/plugin-search": "^1.4.2-next.1", ++ "@backstage/plugin-search-react": "^1.7.2-next.1", ++ "@backstage/plugin-tech-radar": "^0.6.10-next.1", ++ "@backstage/plugin-techdocs": "^1.8.1-next.1", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.2-next.1", ++ "@backstage/plugin-techdocs-react": "^1.1.13-next.1", ++ "@backstage/plugin-user-settings": "^0.7.12-next.1", ++ "@backstage/theme": "^0.4.4-next.0", + "@material-ui/core": "^4.12.2", +@@ -36,29 +46,15 @@ + "react-dom": "^17.0.2", +- "react-router": "6.0.0-beta.0", +- "react-router-dom": "6.0.0-beta.0", +- "react-use": "^15.3.3" ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^1.0.0", ++ "@backstage/test-utils": "^1.4.5-next.0", ++ "@playwright/test": "^1.32.3", + "@testing-library/jest-dom": "^5.10.1", + "@testing-library/react": "^12.1.3", +- "@testing-library/user-event": "^12.0.7", +- "@types/jest": "^26.0.7", +- "@types/node": "^14.14.32", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^8.0.0", + "@types/react-dom": "*", +- "cross-env": "^7.0.0", +- "cypress": "^7.3.0", +- "eslint-plugin-cypress": "^2.10.3", +- "start-server-and-test": "^1.10.11" +- }, +- "scripts": { +- "start": "backstage-cli package start", +- "build": "backstage-cli package build", +- "clean": "backstage-cli package clean", +- "test": "backstage-cli package test", +- "lint": "backstage-cli package lint", +- "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", +- "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", +- "cy:dev": "cypress open", +- "cy:run": "cypress run" ++ "cross-env": "^7.0.0" + }, +diff --git a/packages/app/public/index.html b/packages/app/public/index.html +index a936c73..fc758ee 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -10,3 +10,2 @@ + /> +- + +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index f4ff424..8d62f29 100644 +--- a/packages/app/src/App.tsx ++++ b/packages/app/src/App.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { Navigate, Route } from 'react-router'; ++import { Navigate, Route } from 'react-router-dom'; + import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; +@@ -21,2 +21,4 @@ import { + } from '@backstage/plugin-techdocs'; ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; + import { UserSettingsPage } from '@backstage/plugin-user-settings'; +@@ -29,5 +31,5 @@ import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; + import { createApp } from '@backstage/app-defaults'; +-import { FlatRoutes } from '@backstage/core-app-api'; ++import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; + import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; +-import { PermissionedRoute } from '@backstage/plugin-permission-react'; ++import { RequirePermission } from '@backstage/plugin-permission-react'; + import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; +@@ -40,2 +42,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -46,2 +49,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -53,8 +57,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -70,3 +71,7 @@ const routes = ( + element={} +- /> ++ > ++ ++ ++ ++ + } /> +@@ -77,6 +82,9 @@ const routes = ( + /> +- } ++ element={ ++ ++ ++ ++ } + /> +@@ -90,4 +98,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -97,5 +105,3 @@ const App = () => ( + +- ++ , + ); +- +-export default App; +diff --git a/packages/app/src/components/Root/LogoFull.tsx b/packages/app/src/components/Root/LogoFull.tsx +index c7b1c84..47e3b73 100644 +--- a/packages/app/src/components/Root/LogoFull.tsx ++++ b/packages/app/src/components/Root/LogoFull.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/LogoIcon.tsx b/packages/app/src/components/Root/LogoIcon.tsx +index 073cf6e..7eae8c7 100644 +--- a/packages/app/src/components/Root/LogoIcon.tsx ++++ b/packages/app/src/components/Root/LogoIcon.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx +index 90738c4..6768b48 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,19 +1,3 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- +-import React, { useContext, PropsWithChildren } from 'react'; +-import { Link, makeStyles } from '@material-ui/core'; ++import React, { PropsWithChildren } from 'react'; ++import { makeStyles } from '@material-ui/core'; + import HomeIcon from '@material-ui/icons/Home'; +@@ -25,3 +9,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -34,3 +17,2 @@ import { + sidebarConfig, +- SidebarContext, + SidebarDivider, +@@ -41,2 +23,4 @@ import { + SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -62,3 +46,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -66,8 +50,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +diff --git a/packages/app/src/components/Root/index.ts b/packages/app/src/components/Root/index.ts +index dff706f..3528881 100644 +--- a/packages/app/src/components/Root/index.ts ++++ b/packages/app/src/components/Root/index.ts +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + export { Root } from './Root'; +diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx +index 84d0944..6722ea2 100644 +--- a/packages/app/src/components/catalog/EntityPage.tsx ++++ b/packages/app/src/components/catalog/EntityPage.tsx +@@ -1,16 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ + import React from 'react'; +@@ -42,2 +27,4 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +@@ -70,2 +57,13 @@ import { + ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); ++ + const cicdContent = ( +@@ -107,2 +105,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -169,3 +175,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -196,3 +202,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -215,3 +221,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -315,3 +321,6 @@ const systemPage = ( + +- ++ ++ ++ ++ + +diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx +index cd4603e..1788dde 100644 +--- a/packages/app/src/components/search/SearchPage.tsx ++++ b/packages/app/src/components/search/SearchPage.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; ++import { makeStyles, Theme, Grid, Paper } from '@material-ui/core'; + +@@ -10,2 +10,3 @@ import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; + ++import { SearchType } from '@backstage/plugin-search'; + import { +@@ -14,6 +15,5 @@ import { + SearchResult, +- SearchType, +- DefaultResultListItem, ++ SearchPagination, + useSearch, +-} from '@backstage/plugin-search'; ++} from '@backstage/plugin-search-react'; + import { +@@ -111,32 +111,6 @@ const SearchPage = () => { + ++ + +- {({ results }) => ( +- +- {results.map(({ type, document }) => { +- switch (type) { +- case 'software-catalog': +- return ( +- +- ); +- case 'techdocs': +- return ( +- +- ); +- default: +- return ( +- +- ); +- } +- })} +- +- )} ++ } /> ++ } /> + +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index dd35d4d..18548e9 100644 +--- a/packages/backend/Dockerfile ++++ b/packages/backend/Dockerfile +@@ -7,3 +7,3 @@ + # yarn tsc +-# yarn build ++# yarn build:backend + # +@@ -11,6 +11,29 @@ + +-FROM node:16-bullseye-slim ++FROM node:18-bookworm-slim + ++# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends python3 g++ build-essential && \ ++ yarn config set python /usr/bin/python3 ++ ++# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, ++# in which case you should also move better-sqlite3 to "devDependencies" in package.json. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends libsqlite3-dev ++ ++# From here on we use the least-privileged `node` user to run the backend. ++USER node ++ ++# This should create the app dir as `node`. ++# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. ++# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. + WORKDIR /app + ++# This switches many Node.js dependencies to production mode. ++ENV NODE_ENV production ++ + # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. +@@ -18,17 +41,12 @@ WORKDIR /app + # and along with yarn.lock and the root package.json, that's enough to run yarn install. +-COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ ++COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ + RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz + +-# install sqlite3 dependencies +-RUN apt-get update && \ +- apt-get install -y libsqlite3-dev python3 cmake g++ && \ +- rm -rf /var/lib/apt/lists/* && \ +- yarn config set python /usr/bin/python3 +- +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + + # Then copy the rest of the backend bundle, along with any other files we might want. +-COPY packages/backend/dist/bundle.tar.gz app-config.yaml ./ ++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"] +diff --git a/packages/backend/README.md b/packages/backend/README.md +index 02426ef..3607b0a 100644 +--- a/packages/backend/README.md ++++ b/packages/backend/README.md +@@ -18,4 +18,2 @@ To run the example backend, first go to the project root and run + yarn install +-yarn tsc +-yarn build + ``` +@@ -27,7 +25,2 @@ After that, go to the `packages/backend` directory and run + ```bash +-AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x \ +-AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x \ +-AUTH_OAUTH2_CLIENT_ID=x AUTH_OAUTH2_CLIENT_SECRET=x \ +-AUTH_OAUTH2_AUTH_URL=x AUTH_OAUTH2_TOKEN_URL=x \ +-LOG_LEVEL=debug \ + yarn start +@@ -35,4 +28,4 @@ yarn start + +-Substitute `x` for actual values, or leave them as dummy values just to try out +-the backend without using the auth or sentry features. ++If you want to override any configuration locally, for example adding any secrets, ++you can do so in `app-config.local.yaml`. + +@@ -45,3 +38,3 @@ locations to the backend. These are places where the backend can find some + entity descriptor data to consume and serve. For more information, see +-[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog). ++[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). + +@@ -65,2 +58,2 @@ and + - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +-- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) ++- [Backstage Documentation](https://backstage.io/docs) +diff --git a/packages/backend/package.json b/packages/backend/package.json +index f5d216b..f224eaf 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -15,24 +15,24 @@ + "clean": "backstage-cli package clean", +- "build-image": "docker build ../.. -f Dockerfile --tag backstage", +- "migrate:create": "knex migrate:make -x ts" ++ "build-image": "docker build ../.. -f Dockerfile --tag backstage" + }, + "dependencies": { ++ "@backstage/backend-common": "^0.19.9-next.1", ++ "@backstage/backend-tasks": "^0.5.12-next.1", ++ "@backstage/catalog-client": "^1.4.5", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/config": "^1.1.1", ++ "@backstage/plugin-app-backend": "^0.3.55-next.1", ++ "@backstage/plugin-auth-backend": "^0.20.0-next.1", ++ "@backstage/plugin-auth-node": "^0.4.1-next.1", ++ "@backstage/plugin-catalog-backend": "^1.15.0-next.1", ++ "@backstage/plugin-permission-common": "^0.7.9", ++ "@backstage/plugin-permission-node": "^0.7.18-next.1", ++ "@backstage/plugin-proxy-backend": "^0.4.5-next.1", ++ "@backstage/plugin-scaffolder-backend": "^1.19.0-next.1", ++ "@backstage/plugin-search-backend": "^1.4.7-next.1", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.16-next.1", ++ "@backstage/plugin-search-backend-node": "^1.2.11-next.1", ++ "@backstage/plugin-techdocs-backend": "^1.9.0-next.1", + "app": "link:../app", +- "@backstage/backend-common": "^0.13.1", +- "@backstage/backend-tasks": "^0.2.1", +- "@backstage/catalog-model": "^1.0.0", +- "@backstage/catalog-client": "^1.0.0", +- "@backstage/config": "^1.0.0", +- "@backstage/plugin-app-backend": "^0.3.30", +- "@backstage/plugin-auth-backend": "^0.12.2", +- "@backstage/plugin-catalog-backend": "^1.0.0", +- "@backstage/plugin-permission-common": "^0.5.3", +- "@backstage/plugin-permission-node": "^0.5.5", +- "@backstage/plugin-proxy-backend": "^0.2.24", +- "@backstage/plugin-scaffolder-backend": "^1.0.0", +- "@backstage/plugin-search-backend": "^0.4.8", +- "@backstage/plugin-search-backend-node": "^0.5.2", +- "@backstage/plugin-techdocs-backend": "^1.0.0", +- "@gitbeaker/node": "^34.6.0", +- "@octokit/rest": "^18.5.3", ++ "better-sqlite3": "^8.0.0", + "dockerode": "^3.3.1", +@@ -40,4 +40,3 @@ + "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "better-sqlite3": "^7.5.0", ++ "pg": "^8.3.0", + "winston": "^3.2.1" +@@ -45,6 +44,7 @@ + "devDependencies": { +- "@backstage/cli": "^0.16.0", ++ "@backstage/cli": "^0.24.0-next.1", + "@types/dockerode": "^3.3.0", + "@types/express": "^4.17.6", +- "@types/express-serve-static-core": "^4.17.5" ++ "@types/express-serve-static-core": "^4.17.5", ++ "@types/luxon": "^2.0.4" + }, +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 70bc66b..04c4ff9 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -17,3 +17,3 @@ import { + DatabaseManager, +- SingleHostDiscovery, ++ HostDiscovery, + UrlReaders, +@@ -32,2 +32,3 @@ import { PluginEnvironment } from './types'; + import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; + +@@ -36,7 +37,11 @@ function makeCreateEnv(config: Config) { + const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++ const discovery = HostDiscovery.fromConfig(config); + const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++ const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); + const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); ++ const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); ++ ++ const identity = DefaultIdentityClient.create({ ++ discovery, ++ }); + const permissions = ServerPermissionClient.fromConfig(config, { +@@ -63,2 +68,3 @@ function makeCreateEnv(config: Config) { + permissions, ++ identity, + }; +@@ -106,3 +112,3 @@ module.hot?.accept(); + main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); ++ console.error('Backend failed to start up', error); + process.exit(1); +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +index 1476e66..77eb6aa 100644 +--- a/packages/backend/src/plugins/auth.ts ++++ b/packages/backend/src/plugins/auth.ts +@@ -1,2 +1,6 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; ++import { ++ createRouter, ++ providers, ++ defaultAuthProviderFactories, ++} from '@backstage/plugin-auth-backend'; + import { Router } from 'express'; +@@ -13,2 +17,37 @@ export default async function createPlugin( + tokenManager: env.tokenManager, ++ providerFactories: { ++ ...defaultAuthProviderFactories, ++ ++ // This replaces the default GitHub auth provider with a customized one. ++ // The `signIn` option enables sign-in for this provider, using the ++ // identity resolution logic that's provided in the `resolver` callback. ++ // ++ // This particular resolver makes all users share a single "guest" identity. ++ // It should only be used for testing and trying out Backstage. ++ // ++ // If you want to use a production ready resolver you can switch to ++ // the one that is commented out below, it looks up a user entity in the ++ // catalog using the GitHub username of the authenticated user. ++ // That resolver requires you to have user entities populated in the catalog, ++ // for example using https://backstage.io/docs/integrations/github/org ++ // ++ // There are other resolvers to choose from, and you can also create ++ // your own, see the auth documentation for more details: ++ // ++ // https://backstage.io/docs/auth/identity-resolver ++ github: providers.github.create({ ++ signIn: { ++ resolver(_, ctx) { ++ const userRef = 'user:default/guest'; // Must be a full entity reference ++ return ctx.issueToken({ ++ claims: { ++ sub: userRef, // The user's own identity ++ ent: [userRef], // A list of identities that the user claims ownership through ++ }, ++ }); ++ }, ++ // resolver: providers.github.resolvers.usernameMatchingUserEntityName(), ++ }, ++ }), ++ }, + }); +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +index 7ce5fcf..a12fee2 100644 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ b/packages/backend/src/plugins/scaffolder.ts +@@ -18,2 +18,4 @@ export default async function createPlugin( + catalogClient, ++ identity: env.identity, ++ permissions: env.permissions, + }); +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +index 8df6b0a..e9469dc 100644 +--- a/packages/backend/src/plugins/search.ts ++++ b/packages/backend/src/plugins/search.ts +@@ -23,2 +23,10 @@ export default async function createPlugin( + ++ const schedule = env.scheduler.createScheduledTaskRunner({ ++ frequency: { minutes: 10 }, ++ timeout: { minutes: 15 }, ++ // A 3 second delay gives the backend server a chance to initialize before ++ // any collators are executed, which may attempt requests against the API. ++ initialDelay: { seconds: 3 }, ++ }); ++ + // Collators are responsible for gathering documents known to plugins. This +@@ -26,3 +34,3 @@ export default async function createPlugin( + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, ++ schedule, + factory: DefaultCatalogCollatorFactory.fromConfig(env.config, { +@@ -35,3 +43,3 @@ export default async function createPlugin( + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, ++ schedule, + factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { +@@ -46,6 +54,4 @@ export default async function createPlugin( + const { scheduler } = await indexBuilder.build(); ++ scheduler.start(); + +- // A 3 second delay gives the backend server a chance to initialize before +- // any collators are executed, which may attempt requests against the API. +- setTimeout(() => scheduler.start(), 3000); + useHotCleanup(module, () => scheduler.stop()); +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +index 0862b0e..9cd2c74 100644 +--- a/packages/backend/src/types.ts ++++ b/packages/backend/src/types.ts +@@ -10,3 +10,4 @@ import { + import { PluginTaskScheduler } from '@backstage/backend-tasks'; +-import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; ++import { PermissionEvaluator } from '@backstage/plugin-permission-common'; ++import { IdentityApi } from '@backstage/plugin-auth-node'; + +@@ -21,3 +22,4 @@ export type PluginEnvironment = { + scheduler: PluginTaskScheduler; +- permissions: PermissionAuthorizer; ++ permissions: PermissionEvaluator; ++ identity: IdentityApi; + }; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..37c7fb1 +--- /dev/null ++++ b/playwright.config.ts +@@ -0,0 +1,60 @@ ++/* ++ * Copyright 2023 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { defineConfig } from '@playwright/test'; ++import { generateProjects } from '@backstage/e2e-test-utils/playwright'; ++ ++/** ++ * See https://playwright.dev/docs/test-configuration. ++ */ ++export default defineConfig({ ++ timeout: 60_000, ++ ++ expect: { ++ timeout: 5_000, ++ }, ++ ++ // Run your local dev server before starting the tests ++ webServer: process.env.CI ++ ? [] ++ : [ ++ { ++ command: 'yarn start', ++ port: 3000, ++ reuseExistingServer: true, ++ timeout: 60_000, ++ }, ++ ], ++ ++ forbidOnly: !!process.env.CI, ++ ++ retries: process.env.CI ? 2 : 0, ++ ++ reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]], ++ ++ use: { ++ actionTimeout: 0, ++ baseURL: ++ process.env.PLAYWRIGHT_URL ?? ++ (process.env.CI ? 'http://localhost:7007' : 'http://localhost:3000'), ++ screenshot: 'only-on-failure', ++ trace: 'on-first-retry', ++ }, ++ ++ outputDir: 'node_modules/.cache/e2e-test-results', ++ ++ projects: generateProjects(), // Find all packages with e2e-test folders ++}); +diff --git a/plugins/README.md b/plugins/README.md +new file mode 100644 +index 0000000..d7865fd +--- /dev/null ++++ b/plugins/README.md +@@ -0,0 +1,9 @@ ++# The Plugins Folder ++ ++This is where your own plugins and their associated modules live, each in a ++separate folder of its own. ++ ++If you want to create a new plugin here, go to your project root directory, run ++the command `yarn new`, and follow the on-screen instructions. ++ ++You can also check out existing plugins on [the plugin marketplace](https://backstage.io/plugins)! +diff --git a/yarn.lock b/yarn.lock +new file mode 100644 +index 0000000..5ad7fe2 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1 @@ ++# intentionally left empty diff --git a/diffs/0.4.25..0.5.7-next.1.diff b/diffs/0.4.25..0.5.7-next.1.diff new file mode 100644 index 00000000..8bf83dbd --- /dev/null +++ b/diffs/0.4.25..0.5.7-next.1.diff @@ -0,0 +1,1401 @@ +diff --git a/.dockerignore b/.dockerignore +index 63c9c34..05edb62 100644 +--- a/.dockerignore ++++ b/.dockerignore +@@ -1,5 +1,8 @@ + .git ++.yarn/cache ++.yarn/install-state.gz + node_modules +-packages +-!packages/backend/dist ++packages/*/src ++packages/*/node_modules + plugins ++*.local.yaml +diff --git a/.eslintignore b/.eslintignore +new file mode 100644 +index 0000000..e5b1994 +--- /dev/null ++++ b/.eslintignore +@@ -0,0 +1 @@ ++playwright.config.ts +diff --git a/.gitignore b/.gitignore +index d16a8d3..fbf8139 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -17,2 +17,11 @@ node_modules/ + ++# Yarn 3 files ++.pnp.* ++.yarn/* ++!.yarn/patches ++!.yarn/plugins ++!.yarn/releases ++!.yarn/sdks ++!.yarn/versions ++ + # Node version directives +@@ -39 +48,7 @@ site + *-credentials.yaml ++ ++# vscode database functionality support files ++*.session.sql ++ ++# E2E test reports ++e2e-test-report/ +diff --git a/app-config.production.yaml b/app-config.production.yaml +index 5e36c23..8f0751c 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -1,3 +1,3 @@ + app: +- # Should be the same as backend.baseUrl when using the `app-backend` plugin ++ # Should be the same as backend.baseUrl when using the `app-backend` plugin. + baseUrl: http://localhost:7007 +@@ -5,4 +5,31 @@ app: + backend: ++ # Note that the baseUrl should be the URL that the browser and other clients ++ # should use when communicating with the backend, i.e. it needs to be ++ # reachable not just from within the backend host, but from all of your ++ # callers. When its value is "http://localhost:7007", it's strictly private ++ # and can't be reached by others. + baseUrl: http://localhost:7007 +- listen: +- port: 7007 ++ # The listener can also be expressed as a single : string. In this case we bind to ++ # all interfaces, the most permissive setting. The right value depends on your specific deployment. ++ listen: ':7007' ++ ++ # config options: https://node-postgres.com/api/client ++ database: ++ client: pg ++ connection: ++ host: ${POSTGRES_HOST} ++ port: ${POSTGRES_PORT} ++ user: ${POSTGRES_USER} ++ password: ${POSTGRES_PASSWORD} ++ # https://node-postgres.com/features/ssl ++ # you can set the sslmode configuration option via the `PGSSLMODE` environment variable ++ # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require) ++ # ssl: ++ # ca: # if you have a CA file and want to verify it you can uncomment this section ++ # $file: /ca/server.crt ++ ++catalog: ++ # Overrides the default list locations from app-config.yaml as these contain example data. ++ # See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details ++ # on how to get entities into the catalog. ++ locations: [] +diff --git a/app-config.yaml b/app-config.yaml +index e7732cf..08d2abf 100644 +--- a/app-config.yaml ++++ b/app-config.yaml +@@ -9,3 +9,3 @@ backend: + # Used for enabling authentication, secret is shared by all backend plugins +- # See https://backstage.io/docs/tutorials/backend-to-backend-auth for ++ # See https://backstage.io/docs/auth/service-to-service-auth for + # information on the format +@@ -17,2 +17,4 @@ backend: + port: 7007 ++ # Uncomment the following host directive to bind to specific interfaces ++ # host: 127.0.0.1 + csp: +@@ -23,4 +25,6 @@ backend: + origin: http://localhost:3000 +- methods: [GET, POST, PUT, DELETE] ++ methods: [GET, HEAD, PATCH, POST, PUT, DELETE] + credentials: true ++ # This is for local development only, it is not recommended to use this in production ++ # The production database configuration is stored in app-config.production.yaml + database: +@@ -28,4 +32,2 @@ backend: + connection: ':memory:' +- cache: +- store: memory + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir +@@ -35,2 +37,4 @@ integrations: + - host: github.com ++ # This is a Personal Access Token or PAT from GitHub. You can find out how to generate this token, and more information ++ # about setting up the GitHub integration here: https://backstage.io/docs/getting-started/configuration#setting-up-a-github-integration + token: ${GITHUB_TOKEN} +@@ -42,5 +46,8 @@ integrations: + proxy: +- '/test': +- target: 'https://example.com' +- changeOrigin: true ++ ### Example for how to add a proxy endpoint for the frontend. ++ ### A typical reason to do this is to handle HTTPS and CORS for internal services. ++ # endpoints: ++ # '/test': ++ # target: 'https://example.com' ++ # changeOrigin: true + +@@ -69,36 +76,28 @@ catalog: + rules: +- - allow: [Component, System, API, Group, User, Resource, Location] ++ - allow: [Component, System, API, Resource, Location] + locations: +- # Backstage example components +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-components.yaml ++ # Local example data, file locations are relative to the backend process, typically `packages/backend` ++ - type: file ++ target: ../../examples/entities.yaml + +- # Backstage example systems +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-systems.yaml +- +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml +- +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml +- +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.yaml +- +- # Backstage example templates +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/react-ssr-template/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/springboot-grpc-template/template.yaml ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml + rules: + - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/docs-template/template.yaml ++ ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml + rules: +- - allow: [Template] ++ - allow: [User, Group] ++ ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml ++ ++ ## Uncomment these lines to add an example org ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme-corp.yaml ++ # rules: ++ # - allow: [User, Group] +diff --git a/backstage.json b/backstage.json +index bbea0d7..707af29 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "1.0.3" ++ "version": "1.20.0-next.1" + } +diff --git a/examples/entities.yaml b/examples/entities.yaml +new file mode 100644 +index 0000000..447e8b1 +--- /dev/null ++++ b/examples/entities.yaml +@@ -0,0 +1,41 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system ++apiVersion: backstage.io/v1alpha1 ++kind: System ++metadata: ++ name: examples ++spec: ++ owner: guests ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: example-website ++spec: ++ type: website ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ providesApis: [example-grpc-api] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api ++apiVersion: backstage.io/v1alpha1 ++kind: API ++metadata: ++ name: example-grpc-api ++spec: ++ type: grpc ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ definition: | ++ syntax = "proto3"; ++ ++ service Exampler { ++ rpc Example (ExampleMessage) returns (ExampleMessage) {}; ++ } ++ ++ message ExampleMessage { ++ string example = 1; ++ }; +diff --git a/examples/org.yaml b/examples/org.yaml +new file mode 100644 +index 0000000..a10e81f +--- /dev/null ++++ b/examples/org.yaml +@@ -0,0 +1,17 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user ++apiVersion: backstage.io/v1alpha1 ++kind: User ++metadata: ++ name: guest ++spec: ++ memberOf: [guests] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group ++apiVersion: backstage.io/v1alpha1 ++kind: Group ++metadata: ++ name: guests ++spec: ++ type: team ++ children: [] +diff --git a/examples/template/content/catalog-info.yaml b/examples/template/content/catalog-info.yaml +new file mode 100644 +index 0000000..d4ccca4 +--- /dev/null ++++ b/examples/template/content/catalog-info.yaml +@@ -0,0 +1,8 @@ ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: ${{ values.name | dump }} ++spec: ++ type: service ++ owner: user:guest ++ lifecycle: experimental +diff --git a/examples/template/content/index.js b/examples/template/content/index.js +new file mode 100644 +index 0000000..071ce5a +--- /dev/null ++++ b/examples/template/content/index.js +@@ -0,0 +1 @@ ++console.log('Hello from ${{ values.name }}!'); +diff --git a/examples/template/content/package.json b/examples/template/content/package.json +new file mode 100644 +index 0000000..86f968a +--- /dev/null ++++ b/examples/template/content/package.json +@@ -0,0 +1,5 @@ ++{ ++ "name": "${{ values.name }}", ++ "private": true, ++ "dependencies": {} ++} +diff --git a/examples/template/template.yaml b/examples/template/template.yaml +new file mode 100644 +index 0000000..33f262b +--- /dev/null ++++ b/examples/template/template.yaml +@@ -0,0 +1,74 @@ ++apiVersion: scaffolder.backstage.io/v1beta3 ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template ++kind: Template ++metadata: ++ name: example-nodejs-template ++ title: Example Node.js Template ++ description: An example template for the scaffolder that creates a simple Node.js service ++spec: ++ owner: user:guest ++ type: service ++ ++ # These parameters are used to generate the input form in the frontend, and are ++ # used to gather input data for the execution of the template. ++ parameters: ++ - title: Fill in some steps ++ required: ++ - name ++ properties: ++ name: ++ title: Name ++ type: string ++ description: Unique name of the component ++ ui:autofocus: true ++ ui:options: ++ rows: 5 ++ - title: Choose a location ++ required: ++ - repoUrl ++ properties: ++ repoUrl: ++ title: Repository Location ++ type: string ++ ui:field: RepoUrlPicker ++ ui:options: ++ allowedHosts: ++ - github.com ++ ++ # These steps are executed in the scaffolder backend, using data that we gathered ++ # via the parameters above. ++ steps: ++ # Each step executes an action, in this case one templates files into the working directory. ++ - id: fetch-base ++ name: Fetch Base ++ action: fetch:template ++ input: ++ url: ./content ++ values: ++ name: ${{ parameters.name }} ++ ++ # This step publishes the contents of the working directory to GitHub. ++ - id: publish ++ name: Publish ++ action: publish:github ++ input: ++ allowedHosts: ['github.com'] ++ description: This is ${{ parameters.name }} ++ repoUrl: ${{ parameters.repoUrl }} ++ ++ # The final step is to register our new component in the catalog. ++ - id: register ++ name: Register ++ action: catalog:register ++ input: ++ repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} ++ catalogInfoPath: '/catalog-info.yaml' ++ ++ # Outputs are displayed to the user after a successful execution of the template. ++ output: ++ links: ++ - title: Repository ++ url: ${{ steps['publish'].output.remoteUrl }} ++ - title: Open in catalog ++ icon: catalog ++ entityRef: ${{ steps['register'].output.entityRef }} +diff --git a/lerna.json b/lerna.json +index 322929d..529a62f 100644 +--- a/lerna.json ++++ b/lerna.json +@@ -3,4 +3,4 @@ + "npmClient": "yarn", +- "useWorkspaces": true, +- "version": "0.1.0" ++ "version": "0.1.0", ++ "$schema": "node_modules/lerna/schemas/lerna-schema.json" + } +diff --git a/package.json b/package.json +index 5b7bfbf..2364da8 100644 +--- a/package.json ++++ b/package.json +@@ -5,3 +5,3 @@ + "engines": { +- "node": "14 || 16" ++ "node": "18 || 20" + }, +@@ -11,3 +11,4 @@ + "start-backend": "yarn workspace backend start", +- "build": "backstage-cli repo build --all", ++ "build:backend": "yarn workspace backend build", ++ "build:all": "backstage-cli repo build --all", + "build-image": "yarn workspace backend build-image", +@@ -15,6 +16,7 @@ + "tsc:full": "tsc --skipLibCheck false --incremental false", +- "clean": "backstage-cli clean && lerna run clean", +- "diff": "lerna run diff --", +- "test": "backstage-cli test", +- "test:all": "lerna run test -- --coverage", ++ "clean": "backstage-cli repo clean", ++ "test": "backstage-cli repo test", ++ "test:all": "backstage-cli repo test --coverage", ++ "test:e2e": "playwright test", ++ "fix": "backstage-cli repo fix", + "lint": "backstage-cli repo lint --since origin/master", +@@ -22,4 +24,3 @@ + "prettier:check": "prettier --check .", +- "create-plugin": "backstage-cli create-plugin --scope internal", +- "remove-plugin": "backstage-cli remove-plugin" ++ "new": "backstage-cli new --scope internal" + }, +@@ -32,8 +33,11 @@ + "devDependencies": { +- "@backstage/cli": "^0.16.0", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/e2e-test-utils": "^0.1.0", ++ "@playwright/test": "^1.32.3", + "@spotify/prettier-config": "^12.0.0", +- "concurrently": "^6.0.0", +- "lerna": "^4.0.0", ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^9.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.5.4" ++ "typescript": "~5.2.0" + }, +diff --git a/packages/README.md b/packages/README.md +new file mode 100644 +index 0000000..6327fa0 +--- /dev/null ++++ b/packages/README.md +@@ -0,0 +1,9 @@ ++# The Packages Folder ++ ++This is where your own applications and centrally managed libraries live, each ++in a separate folder of its own. ++ ++From the start there's an `app` folder (for the frontend) and a `backend` folder ++(for the Node backend), but you can also add more modules in here that house ++your core additions and adaptations, such as themes, common React component ++libraries, utilities, and similar. +diff --git a/packages/app/.eslintignore b/packages/app/.eslintignore +new file mode 100644 +index 0000000..a48cf0d +--- /dev/null ++++ b/packages/app/.eslintignore +@@ -0,0 +1 @@ ++public +diff --git a/packages/app/cypress.json b/packages/app/cypress.json +deleted file mode 100644 +index 5de7ebf..0000000 +--- a/packages/app/cypress.json ++++ /dev/null +@@ -1,5 +0,0 @@ +-{ +- "baseUrl": "http://localhost:3001", +- "fixturesFolder": false, +- "pluginsFile": false +-} +diff --git a/packages/app/cypress/.eslintrc.json b/packages/app/cypress/.eslintrc.json +deleted file mode 100644 +index 2b3a458..0000000 +--- a/packages/app/cypress/.eslintrc.json ++++ /dev/null +@@ -1,21 +0,0 @@ +-{ +- "plugins": ["cypress"], +- "extends": ["plugin:cypress/recommended"], +- "rules": { +- "jest/expect-expect": [ +- "error", +- { +- "assertFunctionNames": ["expect", "cy.contains"] +- } +- ], +- "import/no-extraneous-dependencies": [ +- "error", +- { +- "devDependencies": true, +- "optionalDependencies": true, +- "peerDependencies": true, +- "bundledDependencies": true +- } +- ] +- } +-} +diff --git a/packages/app/cypress/integration/app.js b/packages/app/cypress/integration/app.js +deleted file mode 100644 +index 43fb2e3..0000000 +--- a/packages/app/cypress/integration/app.js ++++ /dev/null +@@ -1,6 +0,0 @@ +-describe('App', () => { +- it('should render the catalog', () => { +- cy.visit('/'); +- cy.contains('My Company Catalog'); +- }); +-}); +diff --git a/packages/app/e2e-tests/app.test.ts b/packages/app/e2e-tests/app.test.ts +new file mode 100644 +index 0000000..d45bc0d +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,23 @@ ++/* ++ * Copyright 2020 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { test, expect } from '@playwright/test'; ++ ++test('App should render the welcome page', async ({ page }) => { ++ await page.goto('/'); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index cfe2065..2100405 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -8,25 +8,35 @@ + }, ++ "scripts": { ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "clean": "backstage-cli package clean", ++ "test": "backstage-cli package test", ++ "lint": "backstage-cli package lint" ++ }, + "dependencies": { +- "@backstage/app-defaults": "^1.0.0", +- "@backstage/catalog-model": "^1.0.0", +- "@backstage/cli": "^0.16.0", +- "@backstage/core-app-api": "^1.0.0", +- "@backstage/core-components": "^0.9.2", +- "@backstage/core-plugin-api": "^1.0.0", +- "@backstage/integration-react": "^1.0.0", +- "@backstage/plugin-api-docs": "^0.8.3", +- "@backstage/plugin-catalog": "^1.0.0", +- "@backstage/plugin-catalog-common": "^1.0.0", +- "@backstage/plugin-catalog-graph": "^0.2.15", +- "@backstage/plugin-catalog-import": "^0.8.6", +- "@backstage/plugin-catalog-react": "^1.0.0", +- "@backstage/plugin-github-actions": "^0.5.3", +- "@backstage/plugin-org": "^0.5.3", +- "@backstage/plugin-permission-react": "^0.3.4", +- "@backstage/plugin-scaffolder": "^1.0.1", +- "@backstage/plugin-search": "^0.7.4", +- "@backstage/plugin-tech-radar": "^0.5.10", +- "@backstage/plugin-techdocs": "^1.0.1", +- "@backstage/plugin-user-settings": "^0.4.2", +- "@backstage/theme": "^0.2.15", ++ "@backstage/app-defaults": "^1.4.5-next.1", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/core-app-api": "^1.11.1-next.0", ++ "@backstage/core-components": "^0.13.8-next.1", ++ "@backstage/core-plugin-api": "^1.8.0-next.0", ++ "@backstage/integration-react": "^1.1.21-next.1", ++ "@backstage/plugin-api-docs": "^0.9.13-next.1", ++ "@backstage/plugin-catalog": "^1.15.0-next.1", ++ "@backstage/plugin-catalog-common": "^1.0.17", ++ "@backstage/plugin-catalog-graph": "^0.2.38-next.1", ++ "@backstage/plugin-catalog-import": "^0.10.2-next.1", ++ "@backstage/plugin-catalog-react": "^1.9.0-next.1", ++ "@backstage/plugin-github-actions": "^0.6.7-next.1", ++ "@backstage/plugin-org": "^0.6.16-next.1", ++ "@backstage/plugin-permission-react": "^0.4.17-next.0", ++ "@backstage/plugin-scaffolder": "^1.16.0-next.1", ++ "@backstage/plugin-search": "^1.4.2-next.1", ++ "@backstage/plugin-search-react": "^1.7.2-next.1", ++ "@backstage/plugin-tech-radar": "^0.6.10-next.1", ++ "@backstage/plugin-techdocs": "^1.8.1-next.1", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.2-next.1", ++ "@backstage/plugin-techdocs-react": "^1.1.13-next.1", ++ "@backstage/plugin-user-settings": "^0.7.12-next.1", ++ "@backstage/theme": "^0.4.4-next.0", + "@material-ui/core": "^4.12.2", +@@ -36,29 +46,15 @@ + "react-dom": "^17.0.2", +- "react-router": "6.0.0-beta.0", +- "react-router-dom": "6.0.0-beta.0", +- "react-use": "^15.3.3" ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^1.0.0", ++ "@backstage/test-utils": "^1.4.5-next.0", ++ "@playwright/test": "^1.32.3", + "@testing-library/jest-dom": "^5.10.1", + "@testing-library/react": "^12.1.3", +- "@testing-library/user-event": "^12.0.7", +- "@types/jest": "^26.0.7", +- "@types/node": "^14.14.32", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^8.0.0", + "@types/react-dom": "*", +- "cross-env": "^7.0.0", +- "cypress": "^7.3.0", +- "eslint-plugin-cypress": "^2.10.3", +- "start-server-and-test": "^1.10.11" +- }, +- "scripts": { +- "start": "backstage-cli package start", +- "build": "backstage-cli package build", +- "clean": "backstage-cli package clean", +- "test": "backstage-cli package test", +- "lint": "backstage-cli package lint", +- "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", +- "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", +- "cy:dev": "cypress open", +- "cy:run": "cypress run" ++ "cross-env": "^7.0.0" + }, +diff --git a/packages/app/public/index.html b/packages/app/public/index.html +index a936c73..fc758ee 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -10,3 +10,2 @@ + /> +- + +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index f4ff424..8d62f29 100644 +--- a/packages/app/src/App.tsx ++++ b/packages/app/src/App.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { Navigate, Route } from 'react-router'; ++import { Navigate, Route } from 'react-router-dom'; + import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; +@@ -21,2 +21,4 @@ import { + } from '@backstage/plugin-techdocs'; ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; + import { UserSettingsPage } from '@backstage/plugin-user-settings'; +@@ -29,5 +31,5 @@ import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; + import { createApp } from '@backstage/app-defaults'; +-import { FlatRoutes } from '@backstage/core-app-api'; ++import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; + import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; +-import { PermissionedRoute } from '@backstage/plugin-permission-react'; ++import { RequirePermission } from '@backstage/plugin-permission-react'; + import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; +@@ -40,2 +42,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -46,2 +49,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -53,8 +57,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -70,3 +71,7 @@ const routes = ( + element={} +- /> ++ > ++ ++ ++ ++ + } /> +@@ -77,6 +82,9 @@ const routes = ( + /> +- } ++ element={ ++ ++ ++ ++ } + /> +@@ -90,4 +98,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -97,5 +105,3 @@ const App = () => ( + +- ++ , + ); +- +-export default App; +diff --git a/packages/app/src/components/Root/LogoFull.tsx b/packages/app/src/components/Root/LogoFull.tsx +index c7b1c84..47e3b73 100644 +--- a/packages/app/src/components/Root/LogoFull.tsx ++++ b/packages/app/src/components/Root/LogoFull.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/LogoIcon.tsx b/packages/app/src/components/Root/LogoIcon.tsx +index 073cf6e..7eae8c7 100644 +--- a/packages/app/src/components/Root/LogoIcon.tsx ++++ b/packages/app/src/components/Root/LogoIcon.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx +index 90738c4..6768b48 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,19 +1,3 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- +-import React, { useContext, PropsWithChildren } from 'react'; +-import { Link, makeStyles } from '@material-ui/core'; ++import React, { PropsWithChildren } from 'react'; ++import { makeStyles } from '@material-ui/core'; + import HomeIcon from '@material-ui/icons/Home'; +@@ -25,3 +9,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -34,3 +17,2 @@ import { + sidebarConfig, +- SidebarContext, + SidebarDivider, +@@ -41,2 +23,4 @@ import { + SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -62,3 +46,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -66,8 +50,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +diff --git a/packages/app/src/components/Root/index.ts b/packages/app/src/components/Root/index.ts +index dff706f..3528881 100644 +--- a/packages/app/src/components/Root/index.ts ++++ b/packages/app/src/components/Root/index.ts +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + export { Root } from './Root'; +diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx +index 84d0944..6722ea2 100644 +--- a/packages/app/src/components/catalog/EntityPage.tsx ++++ b/packages/app/src/components/catalog/EntityPage.tsx +@@ -1,16 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ + import React from 'react'; +@@ -42,2 +27,4 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +@@ -70,2 +57,13 @@ import { + ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); ++ + const cicdContent = ( +@@ -107,2 +105,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -169,3 +175,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -196,3 +202,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -215,3 +221,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -315,3 +321,6 @@ const systemPage = ( + +- ++ ++ ++ ++ + +diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx +index cd4603e..1788dde 100644 +--- a/packages/app/src/components/search/SearchPage.tsx ++++ b/packages/app/src/components/search/SearchPage.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; ++import { makeStyles, Theme, Grid, Paper } from '@material-ui/core'; + +@@ -10,2 +10,3 @@ import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; + ++import { SearchType } from '@backstage/plugin-search'; + import { +@@ -14,6 +15,5 @@ import { + SearchResult, +- SearchType, +- DefaultResultListItem, ++ SearchPagination, + useSearch, +-} from '@backstage/plugin-search'; ++} from '@backstage/plugin-search-react'; + import { +@@ -111,32 +111,6 @@ const SearchPage = () => { + ++ + +- {({ results }) => ( +- +- {results.map(({ type, document }) => { +- switch (type) { +- case 'software-catalog': +- return ( +- +- ); +- case 'techdocs': +- return ( +- +- ); +- default: +- return ( +- +- ); +- } +- })} +- +- )} ++ } /> ++ } /> + +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index dd35d4d..18548e9 100644 +--- a/packages/backend/Dockerfile ++++ b/packages/backend/Dockerfile +@@ -7,3 +7,3 @@ + # yarn tsc +-# yarn build ++# yarn build:backend + # +@@ -11,6 +11,29 @@ + +-FROM node:16-bullseye-slim ++FROM node:18-bookworm-slim + ++# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends python3 g++ build-essential && \ ++ yarn config set python /usr/bin/python3 ++ ++# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, ++# in which case you should also move better-sqlite3 to "devDependencies" in package.json. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends libsqlite3-dev ++ ++# From here on we use the least-privileged `node` user to run the backend. ++USER node ++ ++# This should create the app dir as `node`. ++# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. ++# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. + WORKDIR /app + ++# This switches many Node.js dependencies to production mode. ++ENV NODE_ENV production ++ + # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. +@@ -18,17 +41,12 @@ WORKDIR /app + # and along with yarn.lock and the root package.json, that's enough to run yarn install. +-COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ ++COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ + RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz + +-# install sqlite3 dependencies +-RUN apt-get update && \ +- apt-get install -y libsqlite3-dev python3 cmake g++ && \ +- rm -rf /var/lib/apt/lists/* && \ +- yarn config set python /usr/bin/python3 +- +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + + # Then copy the rest of the backend bundle, along with any other files we might want. +-COPY packages/backend/dist/bundle.tar.gz app-config.yaml ./ ++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"] +diff --git a/packages/backend/README.md b/packages/backend/README.md +index 02426ef..3607b0a 100644 +--- a/packages/backend/README.md ++++ b/packages/backend/README.md +@@ -18,4 +18,2 @@ To run the example backend, first go to the project root and run + yarn install +-yarn tsc +-yarn build + ``` +@@ -27,7 +25,2 @@ After that, go to the `packages/backend` directory and run + ```bash +-AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x \ +-AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x \ +-AUTH_OAUTH2_CLIENT_ID=x AUTH_OAUTH2_CLIENT_SECRET=x \ +-AUTH_OAUTH2_AUTH_URL=x AUTH_OAUTH2_TOKEN_URL=x \ +-LOG_LEVEL=debug \ + yarn start +@@ -35,4 +28,4 @@ yarn start + +-Substitute `x` for actual values, or leave them as dummy values just to try out +-the backend without using the auth or sentry features. ++If you want to override any configuration locally, for example adding any secrets, ++you can do so in `app-config.local.yaml`. + +@@ -45,3 +38,3 @@ locations to the backend. These are places where the backend can find some + entity descriptor data to consume and serve. For more information, see +-[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog). ++[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). + +@@ -65,2 +58,2 @@ and + - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +-- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) ++- [Backstage Documentation](https://backstage.io/docs) +diff --git a/packages/backend/package.json b/packages/backend/package.json +index acbfeda..f224eaf 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -15,24 +15,24 @@ + "clean": "backstage-cli package clean", +- "build-image": "docker build ../.. -f Dockerfile --tag backstage", +- "migrate:create": "knex migrate:make -x ts" ++ "build-image": "docker build ../.. -f Dockerfile --tag backstage" + }, + "dependencies": { ++ "@backstage/backend-common": "^0.19.9-next.1", ++ "@backstage/backend-tasks": "^0.5.12-next.1", ++ "@backstage/catalog-client": "^1.4.5", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/config": "^1.1.1", ++ "@backstage/plugin-app-backend": "^0.3.55-next.1", ++ "@backstage/plugin-auth-backend": "^0.20.0-next.1", ++ "@backstage/plugin-auth-node": "^0.4.1-next.1", ++ "@backstage/plugin-catalog-backend": "^1.15.0-next.1", ++ "@backstage/plugin-permission-common": "^0.7.9", ++ "@backstage/plugin-permission-node": "^0.7.18-next.1", ++ "@backstage/plugin-proxy-backend": "^0.4.5-next.1", ++ "@backstage/plugin-scaffolder-backend": "^1.19.0-next.1", ++ "@backstage/plugin-search-backend": "^1.4.7-next.1", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.16-next.1", ++ "@backstage/plugin-search-backend-node": "^1.2.11-next.1", ++ "@backstage/plugin-techdocs-backend": "^1.9.0-next.1", + "app": "link:../app", +- "@backstage/backend-common": "^0.13.1", +- "@backstage/backend-tasks": "^0.2.1", +- "@backstage/catalog-model": "^1.0.0", +- "@backstage/catalog-client": "^1.0.0", +- "@backstage/config": "^1.0.0", +- "@backstage/plugin-app-backend": "^0.3.30", +- "@backstage/plugin-auth-backend": "^0.12.3", +- "@backstage/plugin-catalog-backend": "^1.0.0", +- "@backstage/plugin-permission-common": "^0.5.3", +- "@backstage/plugin-permission-node": "^0.5.5", +- "@backstage/plugin-proxy-backend": "^0.2.24", +- "@backstage/plugin-scaffolder-backend": "^1.0.0", +- "@backstage/plugin-search-backend": "^0.4.8", +- "@backstage/plugin-search-backend-node": "^0.5.2", +- "@backstage/plugin-techdocs-backend": "^1.0.0", +- "@gitbeaker/node": "^34.6.0", +- "@octokit/rest": "^18.5.3", ++ "better-sqlite3": "^8.0.0", + "dockerode": "^3.3.1", +@@ -40,4 +40,3 @@ + "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "better-sqlite3": "^7.5.0", ++ "pg": "^8.3.0", + "winston": "^3.2.1" +@@ -45,6 +44,7 @@ + "devDependencies": { +- "@backstage/cli": "^0.16.0", ++ "@backstage/cli": "^0.24.0-next.1", + "@types/dockerode": "^3.3.0", + "@types/express": "^4.17.6", +- "@types/express-serve-static-core": "^4.17.5" ++ "@types/express-serve-static-core": "^4.17.5", ++ "@types/luxon": "^2.0.4" + }, +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 70bc66b..04c4ff9 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -17,3 +17,3 @@ import { + DatabaseManager, +- SingleHostDiscovery, ++ HostDiscovery, + UrlReaders, +@@ -32,2 +32,3 @@ import { PluginEnvironment } from './types'; + import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; + +@@ -36,7 +37,11 @@ function makeCreateEnv(config: Config) { + const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++ const discovery = HostDiscovery.fromConfig(config); + const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++ const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); + const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); ++ const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); ++ ++ const identity = DefaultIdentityClient.create({ ++ discovery, ++ }); + const permissions = ServerPermissionClient.fromConfig(config, { +@@ -63,2 +68,3 @@ function makeCreateEnv(config: Config) { + permissions, ++ identity, + }; +@@ -106,3 +112,3 @@ module.hot?.accept(); + main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); ++ console.error('Backend failed to start up', error); + process.exit(1); +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +index 1476e66..77eb6aa 100644 +--- a/packages/backend/src/plugins/auth.ts ++++ b/packages/backend/src/plugins/auth.ts +@@ -1,2 +1,6 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; ++import { ++ createRouter, ++ providers, ++ defaultAuthProviderFactories, ++} from '@backstage/plugin-auth-backend'; + import { Router } from 'express'; +@@ -13,2 +17,37 @@ export default async function createPlugin( + tokenManager: env.tokenManager, ++ providerFactories: { ++ ...defaultAuthProviderFactories, ++ ++ // This replaces the default GitHub auth provider with a customized one. ++ // The `signIn` option enables sign-in for this provider, using the ++ // identity resolution logic that's provided in the `resolver` callback. ++ // ++ // This particular resolver makes all users share a single "guest" identity. ++ // It should only be used for testing and trying out Backstage. ++ // ++ // If you want to use a production ready resolver you can switch to ++ // the one that is commented out below, it looks up a user entity in the ++ // catalog using the GitHub username of the authenticated user. ++ // That resolver requires you to have user entities populated in the catalog, ++ // for example using https://backstage.io/docs/integrations/github/org ++ // ++ // There are other resolvers to choose from, and you can also create ++ // your own, see the auth documentation for more details: ++ // ++ // https://backstage.io/docs/auth/identity-resolver ++ github: providers.github.create({ ++ signIn: { ++ resolver(_, ctx) { ++ const userRef = 'user:default/guest'; // Must be a full entity reference ++ return ctx.issueToken({ ++ claims: { ++ sub: userRef, // The user's own identity ++ ent: [userRef], // A list of identities that the user claims ownership through ++ }, ++ }); ++ }, ++ // resolver: providers.github.resolvers.usernameMatchingUserEntityName(), ++ }, ++ }), ++ }, + }); +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +index 7ce5fcf..a12fee2 100644 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ b/packages/backend/src/plugins/scaffolder.ts +@@ -18,2 +18,4 @@ export default async function createPlugin( + catalogClient, ++ identity: env.identity, ++ permissions: env.permissions, + }); +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +index 8df6b0a..e9469dc 100644 +--- a/packages/backend/src/plugins/search.ts ++++ b/packages/backend/src/plugins/search.ts +@@ -23,2 +23,10 @@ export default async function createPlugin( + ++ const schedule = env.scheduler.createScheduledTaskRunner({ ++ frequency: { minutes: 10 }, ++ timeout: { minutes: 15 }, ++ // A 3 second delay gives the backend server a chance to initialize before ++ // any collators are executed, which may attempt requests against the API. ++ initialDelay: { seconds: 3 }, ++ }); ++ + // Collators are responsible for gathering documents known to plugins. This +@@ -26,3 +34,3 @@ export default async function createPlugin( + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, ++ schedule, + factory: DefaultCatalogCollatorFactory.fromConfig(env.config, { +@@ -35,3 +43,3 @@ export default async function createPlugin( + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, ++ schedule, + factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { +@@ -46,6 +54,4 @@ export default async function createPlugin( + const { scheduler } = await indexBuilder.build(); ++ scheduler.start(); + +- // A 3 second delay gives the backend server a chance to initialize before +- // any collators are executed, which may attempt requests against the API. +- setTimeout(() => scheduler.start(), 3000); + useHotCleanup(module, () => scheduler.stop()); +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +index 0862b0e..9cd2c74 100644 +--- a/packages/backend/src/types.ts ++++ b/packages/backend/src/types.ts +@@ -10,3 +10,4 @@ import { + import { PluginTaskScheduler } from '@backstage/backend-tasks'; +-import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; ++import { PermissionEvaluator } from '@backstage/plugin-permission-common'; ++import { IdentityApi } from '@backstage/plugin-auth-node'; + +@@ -21,3 +22,4 @@ export type PluginEnvironment = { + scheduler: PluginTaskScheduler; +- permissions: PermissionAuthorizer; ++ permissions: PermissionEvaluator; ++ identity: IdentityApi; + }; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..37c7fb1 +--- /dev/null ++++ b/playwright.config.ts +@@ -0,0 +1,60 @@ ++/* ++ * Copyright 2023 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { defineConfig } from '@playwright/test'; ++import { generateProjects } from '@backstage/e2e-test-utils/playwright'; ++ ++/** ++ * See https://playwright.dev/docs/test-configuration. ++ */ ++export default defineConfig({ ++ timeout: 60_000, ++ ++ expect: { ++ timeout: 5_000, ++ }, ++ ++ // Run your local dev server before starting the tests ++ webServer: process.env.CI ++ ? [] ++ : [ ++ { ++ command: 'yarn start', ++ port: 3000, ++ reuseExistingServer: true, ++ timeout: 60_000, ++ }, ++ ], ++ ++ forbidOnly: !!process.env.CI, ++ ++ retries: process.env.CI ? 2 : 0, ++ ++ reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]], ++ ++ use: { ++ actionTimeout: 0, ++ baseURL: ++ process.env.PLAYWRIGHT_URL ?? ++ (process.env.CI ? 'http://localhost:7007' : 'http://localhost:3000'), ++ screenshot: 'only-on-failure', ++ trace: 'on-first-retry', ++ }, ++ ++ outputDir: 'node_modules/.cache/e2e-test-results', ++ ++ projects: generateProjects(), // Find all packages with e2e-test folders ++}); +diff --git a/plugins/README.md b/plugins/README.md +new file mode 100644 +index 0000000..d7865fd +--- /dev/null ++++ b/plugins/README.md +@@ -0,0 +1,9 @@ ++# The Plugins Folder ++ ++This is where your own plugins and their associated modules live, each in a ++separate folder of its own. ++ ++If you want to create a new plugin here, go to your project root directory, run ++the command `yarn new`, and follow the on-screen instructions. ++ ++You can also check out existing plugins on [the plugin marketplace](https://backstage.io/plugins)! +diff --git a/yarn.lock b/yarn.lock +new file mode 100644 +index 0000000..5ad7fe2 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1 @@ ++# intentionally left empty diff --git a/diffs/0.4.26..0.5.7-next.1.diff b/diffs/0.4.26..0.5.7-next.1.diff new file mode 100644 index 00000000..6da04c64 --- /dev/null +++ b/diffs/0.4.26..0.5.7-next.1.diff @@ -0,0 +1,1349 @@ +diff --git a/.dockerignore b/.dockerignore +index 63c9c34..05edb62 100644 +--- a/.dockerignore ++++ b/.dockerignore +@@ -1,5 +1,8 @@ + .git ++.yarn/cache ++.yarn/install-state.gz + node_modules +-packages +-!packages/backend/dist ++packages/*/src ++packages/*/node_modules + plugins ++*.local.yaml +diff --git a/.eslintignore b/.eslintignore +new file mode 100644 +index 0000000..e5b1994 +--- /dev/null ++++ b/.eslintignore +@@ -0,0 +1 @@ ++playwright.config.ts +diff --git a/.gitignore b/.gitignore +index d16a8d3..fbf8139 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -17,2 +17,11 @@ node_modules/ + ++# Yarn 3 files ++.pnp.* ++.yarn/* ++!.yarn/patches ++!.yarn/plugins ++!.yarn/releases ++!.yarn/sdks ++!.yarn/versions ++ + # Node version directives +@@ -39 +48,7 @@ site + *-credentials.yaml ++ ++# vscode database functionality support files ++*.session.sql ++ ++# E2E test reports ++e2e-test-report/ +diff --git a/app-config.production.yaml b/app-config.production.yaml +index 5e36c23..8f0751c 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -1,3 +1,3 @@ + app: +- # Should be the same as backend.baseUrl when using the `app-backend` plugin ++ # Should be the same as backend.baseUrl when using the `app-backend` plugin. + baseUrl: http://localhost:7007 +@@ -5,4 +5,31 @@ app: + backend: ++ # Note that the baseUrl should be the URL that the browser and other clients ++ # should use when communicating with the backend, i.e. it needs to be ++ # reachable not just from within the backend host, but from all of your ++ # callers. When its value is "http://localhost:7007", it's strictly private ++ # and can't be reached by others. + baseUrl: http://localhost:7007 +- listen: +- port: 7007 ++ # The listener can also be expressed as a single : string. In this case we bind to ++ # all interfaces, the most permissive setting. The right value depends on your specific deployment. ++ listen: ':7007' ++ ++ # config options: https://node-postgres.com/api/client ++ database: ++ client: pg ++ connection: ++ host: ${POSTGRES_HOST} ++ port: ${POSTGRES_PORT} ++ user: ${POSTGRES_USER} ++ password: ${POSTGRES_PASSWORD} ++ # https://node-postgres.com/features/ssl ++ # you can set the sslmode configuration option via the `PGSSLMODE` environment variable ++ # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require) ++ # ssl: ++ # ca: # if you have a CA file and want to verify it you can uncomment this section ++ # $file: /ca/server.crt ++ ++catalog: ++ # Overrides the default list locations from app-config.yaml as these contain example data. ++ # See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details ++ # on how to get entities into the catalog. ++ locations: [] +diff --git a/app-config.yaml b/app-config.yaml +index 2f2a14b..08d2abf 100644 +--- a/app-config.yaml ++++ b/app-config.yaml +@@ -9,3 +9,3 @@ backend: + # Used for enabling authentication, secret is shared by all backend plugins +- # See https://backstage.io/docs/tutorials/backend-to-backend-auth for ++ # See https://backstage.io/docs/auth/service-to-service-auth for + # information on the format +@@ -17,2 +17,4 @@ backend: + port: 7007 ++ # Uncomment the following host directive to bind to specific interfaces ++ # host: 127.0.0.1 + csp: +@@ -23,4 +25,6 @@ backend: + origin: http://localhost:3000 +- methods: [GET, POST, PUT, DELETE] ++ methods: [GET, HEAD, PATCH, POST, PUT, DELETE] + credentials: true ++ # This is for local development only, it is not recommended to use this in production ++ # The production database configuration is stored in app-config.production.yaml + database: +@@ -28,4 +32,2 @@ backend: + connection: ':memory:' +- cache: +- store: memory + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir +@@ -35,2 +37,4 @@ integrations: + - host: github.com ++ # This is a Personal Access Token or PAT from GitHub. You can find out how to generate this token, and more information ++ # about setting up the GitHub integration here: https://backstage.io/docs/getting-started/configuration#setting-up-a-github-integration + token: ${GITHUB_TOKEN} +@@ -42,5 +46,8 @@ integrations: + proxy: +- '/test': +- target: 'https://example.com' +- changeOrigin: true ++ ### Example for how to add a proxy endpoint for the frontend. ++ ### A typical reason to do this is to handle HTTPS and CORS for internal services. ++ # endpoints: ++ # '/test': ++ # target: 'https://example.com' ++ # changeOrigin: true + +@@ -71,34 +78,26 @@ catalog: + locations: +- # Backstage example components +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-components.yaml ++ # Local example data, file locations are relative to the backend process, typically `packages/backend` ++ - type: file ++ target: ../../examples/entities.yaml + +- # Backstage example systems +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-systems.yaml +- +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml +- +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml +- +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.yaml +- +- # Backstage example templates +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/react-ssr-template/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/springboot-grpc-template/template.yaml ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml + rules: + - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/docs-template/template.yaml ++ ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml + rules: +- - allow: [Template] ++ - allow: [User, Group] ++ ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml ++ ++ ## Uncomment these lines to add an example org ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme-corp.yaml ++ # rules: ++ # - allow: [User, Group] +diff --git a/backstage.json b/backstage.json +index fabbd9a..707af29 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "1.1.0" ++ "version": "1.20.0-next.1" + } +diff --git a/examples/entities.yaml b/examples/entities.yaml +new file mode 100644 +index 0000000..447e8b1 +--- /dev/null ++++ b/examples/entities.yaml +@@ -0,0 +1,41 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system ++apiVersion: backstage.io/v1alpha1 ++kind: System ++metadata: ++ name: examples ++spec: ++ owner: guests ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: example-website ++spec: ++ type: website ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ providesApis: [example-grpc-api] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api ++apiVersion: backstage.io/v1alpha1 ++kind: API ++metadata: ++ name: example-grpc-api ++spec: ++ type: grpc ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ definition: | ++ syntax = "proto3"; ++ ++ service Exampler { ++ rpc Example (ExampleMessage) returns (ExampleMessage) {}; ++ } ++ ++ message ExampleMessage { ++ string example = 1; ++ }; +diff --git a/examples/org.yaml b/examples/org.yaml +new file mode 100644 +index 0000000..a10e81f +--- /dev/null ++++ b/examples/org.yaml +@@ -0,0 +1,17 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user ++apiVersion: backstage.io/v1alpha1 ++kind: User ++metadata: ++ name: guest ++spec: ++ memberOf: [guests] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group ++apiVersion: backstage.io/v1alpha1 ++kind: Group ++metadata: ++ name: guests ++spec: ++ type: team ++ children: [] +diff --git a/examples/template/content/catalog-info.yaml b/examples/template/content/catalog-info.yaml +new file mode 100644 +index 0000000..d4ccca4 +--- /dev/null ++++ b/examples/template/content/catalog-info.yaml +@@ -0,0 +1,8 @@ ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: ${{ values.name | dump }} ++spec: ++ type: service ++ owner: user:guest ++ lifecycle: experimental +diff --git a/examples/template/content/index.js b/examples/template/content/index.js +new file mode 100644 +index 0000000..071ce5a +--- /dev/null ++++ b/examples/template/content/index.js +@@ -0,0 +1 @@ ++console.log('Hello from ${{ values.name }}!'); +diff --git a/examples/template/content/package.json b/examples/template/content/package.json +new file mode 100644 +index 0000000..86f968a +--- /dev/null ++++ b/examples/template/content/package.json +@@ -0,0 +1,5 @@ ++{ ++ "name": "${{ values.name }}", ++ "private": true, ++ "dependencies": {} ++} +diff --git a/examples/template/template.yaml b/examples/template/template.yaml +new file mode 100644 +index 0000000..33f262b +--- /dev/null ++++ b/examples/template/template.yaml +@@ -0,0 +1,74 @@ ++apiVersion: scaffolder.backstage.io/v1beta3 ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template ++kind: Template ++metadata: ++ name: example-nodejs-template ++ title: Example Node.js Template ++ description: An example template for the scaffolder that creates a simple Node.js service ++spec: ++ owner: user:guest ++ type: service ++ ++ # These parameters are used to generate the input form in the frontend, and are ++ # used to gather input data for the execution of the template. ++ parameters: ++ - title: Fill in some steps ++ required: ++ - name ++ properties: ++ name: ++ title: Name ++ type: string ++ description: Unique name of the component ++ ui:autofocus: true ++ ui:options: ++ rows: 5 ++ - title: Choose a location ++ required: ++ - repoUrl ++ properties: ++ repoUrl: ++ title: Repository Location ++ type: string ++ ui:field: RepoUrlPicker ++ ui:options: ++ allowedHosts: ++ - github.com ++ ++ # These steps are executed in the scaffolder backend, using data that we gathered ++ # via the parameters above. ++ steps: ++ # Each step executes an action, in this case one templates files into the working directory. ++ - id: fetch-base ++ name: Fetch Base ++ action: fetch:template ++ input: ++ url: ./content ++ values: ++ name: ${{ parameters.name }} ++ ++ # This step publishes the contents of the working directory to GitHub. ++ - id: publish ++ name: Publish ++ action: publish:github ++ input: ++ allowedHosts: ['github.com'] ++ description: This is ${{ parameters.name }} ++ repoUrl: ${{ parameters.repoUrl }} ++ ++ # The final step is to register our new component in the catalog. ++ - id: register ++ name: Register ++ action: catalog:register ++ input: ++ repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} ++ catalogInfoPath: '/catalog-info.yaml' ++ ++ # Outputs are displayed to the user after a successful execution of the template. ++ output: ++ links: ++ - title: Repository ++ url: ${{ steps['publish'].output.remoteUrl }} ++ - title: Open in catalog ++ icon: catalog ++ entityRef: ${{ steps['register'].output.entityRef }} +diff --git a/lerna.json b/lerna.json +index 322929d..529a62f 100644 +--- a/lerna.json ++++ b/lerna.json +@@ -3,4 +3,4 @@ + "npmClient": "yarn", +- "useWorkspaces": true, +- "version": "0.1.0" ++ "version": "0.1.0", ++ "$schema": "node_modules/lerna/schemas/lerna-schema.json" + } +diff --git a/package.json b/package.json +index f2955eb..2364da8 100644 +--- a/package.json ++++ b/package.json +@@ -5,3 +5,3 @@ + "engines": { +- "node": "14 || 16" ++ "node": "18 || 20" + }, +@@ -11,3 +11,4 @@ + "start-backend": "yarn workspace backend start", +- "build": "backstage-cli repo build --all", ++ "build:backend": "yarn workspace backend build", ++ "build:all": "backstage-cli repo build --all", + "build-image": "yarn workspace backend build-image", +@@ -15,6 +16,7 @@ + "tsc:full": "tsc --skipLibCheck false --incremental false", +- "clean": "backstage-cli clean && lerna run clean", +- "diff": "lerna run diff --", +- "test": "backstage-cli test", +- "test:all": "lerna run test -- --coverage", ++ "clean": "backstage-cli repo clean", ++ "test": "backstage-cli repo test", ++ "test:all": "backstage-cli repo test --coverage", ++ "test:e2e": "playwright test", ++ "fix": "backstage-cli repo fix", + "lint": "backstage-cli repo lint --since origin/master", +@@ -22,4 +24,3 @@ + "prettier:check": "prettier --check .", +- "create-plugin": "backstage-cli create-plugin --scope internal", +- "remove-plugin": "backstage-cli remove-plugin" ++ "new": "backstage-cli new --scope internal" + }, +@@ -32,8 +33,11 @@ + "devDependencies": { +- "@backstage/cli": "^0.17.0", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/e2e-test-utils": "^0.1.0", ++ "@playwright/test": "^1.32.3", + "@spotify/prettier-config": "^12.0.0", +- "concurrently": "^6.0.0", +- "lerna": "^4.0.0", ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^9.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.5.4" ++ "typescript": "~5.2.0" + }, +diff --git a/packages/app/.eslintignore b/packages/app/.eslintignore +new file mode 100644 +index 0000000..a48cf0d +--- /dev/null ++++ b/packages/app/.eslintignore +@@ -0,0 +1 @@ ++public +diff --git a/packages/app/cypress.json b/packages/app/cypress.json +deleted file mode 100644 +index 5de7ebf..0000000 +--- a/packages/app/cypress.json ++++ /dev/null +@@ -1,5 +0,0 @@ +-{ +- "baseUrl": "http://localhost:3001", +- "fixturesFolder": false, +- "pluginsFile": false +-} +diff --git a/packages/app/cypress/.eslintrc.json b/packages/app/cypress/.eslintrc.json +deleted file mode 100644 +index 2b3a458..0000000 +--- a/packages/app/cypress/.eslintrc.json ++++ /dev/null +@@ -1,21 +0,0 @@ +-{ +- "plugins": ["cypress"], +- "extends": ["plugin:cypress/recommended"], +- "rules": { +- "jest/expect-expect": [ +- "error", +- { +- "assertFunctionNames": ["expect", "cy.contains"] +- } +- ], +- "import/no-extraneous-dependencies": [ +- "error", +- { +- "devDependencies": true, +- "optionalDependencies": true, +- "peerDependencies": true, +- "bundledDependencies": true +- } +- ] +- } +-} +diff --git a/packages/app/cypress/integration/app.js b/packages/app/cypress/integration/app.js +deleted file mode 100644 +index 43fb2e3..0000000 +--- a/packages/app/cypress/integration/app.js ++++ /dev/null +@@ -1,6 +0,0 @@ +-describe('App', () => { +- it('should render the catalog', () => { +- cy.visit('/'); +- cy.contains('My Company Catalog'); +- }); +-}); +diff --git a/packages/app/e2e-tests/app.test.ts b/packages/app/e2e-tests/app.test.ts +new file mode 100644 +index 0000000..d45bc0d +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,23 @@ ++/* ++ * Copyright 2020 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { test, expect } from '@playwright/test'; ++ ++test('App should render the welcome page', async ({ page }) => { ++ await page.goto('/'); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 5509d17..2100405 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -8,26 +8,35 @@ + }, ++ "scripts": { ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "clean": "backstage-cli package clean", ++ "test": "backstage-cli package test", ++ "lint": "backstage-cli package lint" ++ }, + "dependencies": { +- "@backstage/app-defaults": "^1.0.1", +- "@backstage/catalog-model": "^1.0.1", +- "@backstage/cli": "^0.17.0", +- "@backstage/core-app-api": "^1.0.1", +- "@backstage/core-components": "^0.9.3", +- "@backstage/core-plugin-api": "^1.0.1", +- "@backstage/integration-react": "^1.0.1", +- "@backstage/plugin-api-docs": "^0.8.4", +- "@backstage/plugin-catalog": "^1.1.0", +- "@backstage/plugin-catalog-common": "^1.0.1", +- "@backstage/plugin-catalog-graph": "^0.2.16", +- "@backstage/plugin-catalog-import": "^0.8.7", +- "@backstage/plugin-catalog-react": "^1.0.1", +- "@backstage/plugin-github-actions": "^0.5.4", +- "@backstage/plugin-org": "^0.5.4", +- "@backstage/plugin-permission-react": "^0.4.0", +- "@backstage/plugin-scaffolder": "^1.1.0", +- "@backstage/plugin-search": "^0.8.0", +- "@backstage/plugin-search-react": "^0.1.0", +- "@backstage/plugin-tech-radar": "^0.5.11", +- "@backstage/plugin-techdocs": "^1.1.0", +- "@backstage/plugin-user-settings": "^0.4.3", +- "@backstage/theme": "^0.2.15", ++ "@backstage/app-defaults": "^1.4.5-next.1", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/core-app-api": "^1.11.1-next.0", ++ "@backstage/core-components": "^0.13.8-next.1", ++ "@backstage/core-plugin-api": "^1.8.0-next.0", ++ "@backstage/integration-react": "^1.1.21-next.1", ++ "@backstage/plugin-api-docs": "^0.9.13-next.1", ++ "@backstage/plugin-catalog": "^1.15.0-next.1", ++ "@backstage/plugin-catalog-common": "^1.0.17", ++ "@backstage/plugin-catalog-graph": "^0.2.38-next.1", ++ "@backstage/plugin-catalog-import": "^0.10.2-next.1", ++ "@backstage/plugin-catalog-react": "^1.9.0-next.1", ++ "@backstage/plugin-github-actions": "^0.6.7-next.1", ++ "@backstage/plugin-org": "^0.6.16-next.1", ++ "@backstage/plugin-permission-react": "^0.4.17-next.0", ++ "@backstage/plugin-scaffolder": "^1.16.0-next.1", ++ "@backstage/plugin-search": "^1.4.2-next.1", ++ "@backstage/plugin-search-react": "^1.7.2-next.1", ++ "@backstage/plugin-tech-radar": "^0.6.10-next.1", ++ "@backstage/plugin-techdocs": "^1.8.1-next.1", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.2-next.1", ++ "@backstage/plugin-techdocs-react": "^1.1.13-next.1", ++ "@backstage/plugin-user-settings": "^0.7.12-next.1", ++ "@backstage/theme": "^0.4.4-next.0", + "@material-ui/core": "^4.12.2", +@@ -37,29 +46,15 @@ + "react-dom": "^17.0.2", +- "react-router": "6.0.0-beta.0", +- "react-router-dom": "6.0.0-beta.0", +- "react-use": "^15.3.3" ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^1.0.1", ++ "@backstage/test-utils": "^1.4.5-next.0", ++ "@playwright/test": "^1.32.3", + "@testing-library/jest-dom": "^5.10.1", + "@testing-library/react": "^12.1.3", +- "@testing-library/user-event": "^12.0.7", +- "@types/jest": "^26.0.7", +- "@types/node": "^14.14.32", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^8.0.0", + "@types/react-dom": "*", +- "cross-env": "^7.0.0", +- "cypress": "^7.3.0", +- "eslint-plugin-cypress": "^2.10.3", +- "start-server-and-test": "^1.10.11" +- }, +- "scripts": { +- "start": "backstage-cli package start", +- "build": "backstage-cli package build", +- "clean": "backstage-cli package clean", +- "test": "backstage-cli package test", +- "lint": "backstage-cli package lint", +- "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", +- "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", +- "cy:dev": "cypress open", +- "cy:run": "cypress run" ++ "cross-env": "^7.0.0" + }, +diff --git a/packages/app/public/index.html b/packages/app/public/index.html +index a936c73..fc758ee 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -10,3 +10,2 @@ + /> +- + +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index f4ff424..8d62f29 100644 +--- a/packages/app/src/App.tsx ++++ b/packages/app/src/App.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { Navigate, Route } from 'react-router'; ++import { Navigate, Route } from 'react-router-dom'; + import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; +@@ -21,2 +21,4 @@ import { + } from '@backstage/plugin-techdocs'; ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; + import { UserSettingsPage } from '@backstage/plugin-user-settings'; +@@ -29,5 +31,5 @@ import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; + import { createApp } from '@backstage/app-defaults'; +-import { FlatRoutes } from '@backstage/core-app-api'; ++import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; + import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; +-import { PermissionedRoute } from '@backstage/plugin-permission-react'; ++import { RequirePermission } from '@backstage/plugin-permission-react'; + import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; +@@ -40,2 +42,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -46,2 +49,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -53,8 +57,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -70,3 +71,7 @@ const routes = ( + element={} +- /> ++ > ++ ++ ++ ++ + } /> +@@ -77,6 +82,9 @@ const routes = ( + /> +- } ++ element={ ++ ++ ++ ++ } + /> +@@ -90,4 +98,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -97,5 +105,3 @@ const App = () => ( + +- ++ , + ); +- +-export default App; +diff --git a/packages/app/src/components/Root/LogoFull.tsx b/packages/app/src/components/Root/LogoFull.tsx +index c7b1c84..47e3b73 100644 +--- a/packages/app/src/components/Root/LogoFull.tsx ++++ b/packages/app/src/components/Root/LogoFull.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/LogoIcon.tsx b/packages/app/src/components/Root/LogoIcon.tsx +index 073cf6e..7eae8c7 100644 +--- a/packages/app/src/components/Root/LogoIcon.tsx ++++ b/packages/app/src/components/Root/LogoIcon.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx +index 90738c4..6768b48 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,19 +1,3 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- +-import React, { useContext, PropsWithChildren } from 'react'; +-import { Link, makeStyles } from '@material-ui/core'; ++import React, { PropsWithChildren } from 'react'; ++import { makeStyles } from '@material-ui/core'; + import HomeIcon from '@material-ui/icons/Home'; +@@ -25,3 +9,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -34,3 +17,2 @@ import { + sidebarConfig, +- SidebarContext, + SidebarDivider, +@@ -41,2 +23,4 @@ import { + SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -62,3 +46,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -66,8 +50,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +diff --git a/packages/app/src/components/Root/index.ts b/packages/app/src/components/Root/index.ts +index dff706f..3528881 100644 +--- a/packages/app/src/components/Root/index.ts ++++ b/packages/app/src/components/Root/index.ts +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + export { Root } from './Root'; +diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx +index 84d0944..6722ea2 100644 +--- a/packages/app/src/components/catalog/EntityPage.tsx ++++ b/packages/app/src/components/catalog/EntityPage.tsx +@@ -1,16 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ + import React from 'react'; +@@ -42,2 +27,4 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +@@ -70,2 +57,13 @@ import { + ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); ++ + const cicdContent = ( +@@ -107,2 +105,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -169,3 +175,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -196,3 +202,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -215,3 +221,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -315,3 +321,6 @@ const systemPage = ( + +- ++ ++ ++ ++ + +diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx +index 0595e4e..1788dde 100644 +--- a/packages/app/src/components/search/SearchPage.tsx ++++ b/packages/app/src/components/search/SearchPage.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; ++import { makeStyles, Theme, Grid, Paper } from '@material-ui/core'; + +@@ -10,2 +10,3 @@ import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; + ++import { SearchType } from '@backstage/plugin-search'; + import { +@@ -14,6 +15,5 @@ import { + SearchResult, +- SearchType, +- DefaultResultListItem, +-} from '@backstage/plugin-search'; +-import { useSearch } from '@backstage/plugin-search-react'; ++ SearchPagination, ++ useSearch, ++} from '@backstage/plugin-search-react'; + import { +@@ -111,32 +111,6 @@ const SearchPage = () => { + ++ + +- {({ results }) => ( +- +- {results.map(({ type, document }) => { +- switch (type) { +- case 'software-catalog': +- return ( +- +- ); +- case 'techdocs': +- return ( +- +- ); +- default: +- return ( +- +- ); +- } +- })} +- +- )} ++ } /> ++ } /> + +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index dd35d4d..18548e9 100644 +--- a/packages/backend/Dockerfile ++++ b/packages/backend/Dockerfile +@@ -7,3 +7,3 @@ + # yarn tsc +-# yarn build ++# yarn build:backend + # +@@ -11,6 +11,29 @@ + +-FROM node:16-bullseye-slim ++FROM node:18-bookworm-slim + ++# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends python3 g++ build-essential && \ ++ yarn config set python /usr/bin/python3 ++ ++# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, ++# in which case you should also move better-sqlite3 to "devDependencies" in package.json. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends libsqlite3-dev ++ ++# From here on we use the least-privileged `node` user to run the backend. ++USER node ++ ++# This should create the app dir as `node`. ++# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. ++# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. + WORKDIR /app + ++# This switches many Node.js dependencies to production mode. ++ENV NODE_ENV production ++ + # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. +@@ -18,17 +41,12 @@ WORKDIR /app + # and along with yarn.lock and the root package.json, that's enough to run yarn install. +-COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ ++COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ + RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz + +-# install sqlite3 dependencies +-RUN apt-get update && \ +- apt-get install -y libsqlite3-dev python3 cmake g++ && \ +- rm -rf /var/lib/apt/lists/* && \ +- yarn config set python /usr/bin/python3 +- +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + + # Then copy the rest of the backend bundle, along with any other files we might want. +-COPY packages/backend/dist/bundle.tar.gz app-config.yaml ./ ++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"] +diff --git a/packages/backend/README.md b/packages/backend/README.md +index 02426ef..3607b0a 100644 +--- a/packages/backend/README.md ++++ b/packages/backend/README.md +@@ -18,4 +18,2 @@ To run the example backend, first go to the project root and run + yarn install +-yarn tsc +-yarn build + ``` +@@ -27,7 +25,2 @@ After that, go to the `packages/backend` directory and run + ```bash +-AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x \ +-AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x \ +-AUTH_OAUTH2_CLIENT_ID=x AUTH_OAUTH2_CLIENT_SECRET=x \ +-AUTH_OAUTH2_AUTH_URL=x AUTH_OAUTH2_TOKEN_URL=x \ +-LOG_LEVEL=debug \ + yarn start +@@ -35,4 +28,4 @@ yarn start + +-Substitute `x` for actual values, or leave them as dummy values just to try out +-the backend without using the auth or sentry features. ++If you want to override any configuration locally, for example adding any secrets, ++you can do so in `app-config.local.yaml`. + +@@ -45,3 +38,3 @@ locations to the backend. These are places where the backend can find some + entity descriptor data to consume and serve. For more information, see +-[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog). ++[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). + +@@ -65,2 +58,2 @@ and + - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +-- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) ++- [Backstage Documentation](https://backstage.io/docs) +diff --git a/packages/backend/package.json b/packages/backend/package.json +index 8e7730c..f224eaf 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -18,18 +18,21 @@ + "dependencies": { ++ "@backstage/backend-common": "^0.19.9-next.1", ++ "@backstage/backend-tasks": "^0.5.12-next.1", ++ "@backstage/catalog-client": "^1.4.5", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/config": "^1.1.1", ++ "@backstage/plugin-app-backend": "^0.3.55-next.1", ++ "@backstage/plugin-auth-backend": "^0.20.0-next.1", ++ "@backstage/plugin-auth-node": "^0.4.1-next.1", ++ "@backstage/plugin-catalog-backend": "^1.15.0-next.1", ++ "@backstage/plugin-permission-common": "^0.7.9", ++ "@backstage/plugin-permission-node": "^0.7.18-next.1", ++ "@backstage/plugin-proxy-backend": "^0.4.5-next.1", ++ "@backstage/plugin-scaffolder-backend": "^1.19.0-next.1", ++ "@backstage/plugin-search-backend": "^1.4.7-next.1", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.16-next.1", ++ "@backstage/plugin-search-backend-node": "^1.2.11-next.1", ++ "@backstage/plugin-techdocs-backend": "^1.9.0-next.1", + "app": "link:../app", +- "@backstage/backend-common": "^0.13.2", +- "@backstage/backend-tasks": "^0.3.0", +- "@backstage/catalog-model": "^1.0.1", +- "@backstage/catalog-client": "^1.0.1", +- "@backstage/config": "^1.0.0", +- "@backstage/plugin-app-backend": "^0.3.31", +- "@backstage/plugin-auth-backend": "^0.13.0", +- "@backstage/plugin-catalog-backend": "^1.1.0", +- "@backstage/plugin-permission-common": "^0.6.0", +- "@backstage/plugin-permission-node": "^0.6.0", +- "@backstage/plugin-proxy-backend": "^0.2.25", +- "@backstage/plugin-scaffolder-backend": "^1.1.0", +- "@backstage/plugin-search-backend": "^0.5.0", +- "@backstage/plugin-search-backend-node": "^0.6.0", +- "@backstage/plugin-techdocs-backend": "^1.1.0", ++ "better-sqlite3": "^8.0.0", + "dockerode": "^3.3.1", +@@ -37,4 +40,3 @@ + "express-promise-router": "^4.1.0", +- "luxon": "^2.0.2", +- "better-sqlite3": "^7.5.0", ++ "pg": "^8.3.0", + "winston": "^3.2.1" +@@ -42,3 +44,3 @@ + "devDependencies": { +- "@backstage/cli": "^0.17.0", ++ "@backstage/cli": "^0.24.0-next.1", + "@types/dockerode": "^3.3.0", +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 70bc66b..04c4ff9 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -17,3 +17,3 @@ import { + DatabaseManager, +- SingleHostDiscovery, ++ HostDiscovery, + UrlReaders, +@@ -32,2 +32,3 @@ import { PluginEnvironment } from './types'; + import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; + +@@ -36,7 +37,11 @@ function makeCreateEnv(config: Config) { + const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++ const discovery = HostDiscovery.fromConfig(config); + const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++ const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); + const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); ++ const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); ++ ++ const identity = DefaultIdentityClient.create({ ++ discovery, ++ }); + const permissions = ServerPermissionClient.fromConfig(config, { +@@ -63,2 +68,3 @@ function makeCreateEnv(config: Config) { + permissions, ++ identity, + }; +@@ -106,3 +112,3 @@ module.hot?.accept(); + main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); ++ console.error('Backend failed to start up', error); + process.exit(1); +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +index 1476e66..77eb6aa 100644 +--- a/packages/backend/src/plugins/auth.ts ++++ b/packages/backend/src/plugins/auth.ts +@@ -1,2 +1,6 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; ++import { ++ createRouter, ++ providers, ++ defaultAuthProviderFactories, ++} from '@backstage/plugin-auth-backend'; + import { Router } from 'express'; +@@ -13,2 +17,37 @@ export default async function createPlugin( + tokenManager: env.tokenManager, ++ providerFactories: { ++ ...defaultAuthProviderFactories, ++ ++ // This replaces the default GitHub auth provider with a customized one. ++ // The `signIn` option enables sign-in for this provider, using the ++ // identity resolution logic that's provided in the `resolver` callback. ++ // ++ // This particular resolver makes all users share a single "guest" identity. ++ // It should only be used for testing and trying out Backstage. ++ // ++ // If you want to use a production ready resolver you can switch to ++ // the one that is commented out below, it looks up a user entity in the ++ // catalog using the GitHub username of the authenticated user. ++ // That resolver requires you to have user entities populated in the catalog, ++ // for example using https://backstage.io/docs/integrations/github/org ++ // ++ // There are other resolvers to choose from, and you can also create ++ // your own, see the auth documentation for more details: ++ // ++ // https://backstage.io/docs/auth/identity-resolver ++ github: providers.github.create({ ++ signIn: { ++ resolver(_, ctx) { ++ const userRef = 'user:default/guest'; // Must be a full entity reference ++ return ctx.issueToken({ ++ claims: { ++ sub: userRef, // The user's own identity ++ ent: [userRef], // A list of identities that the user claims ownership through ++ }, ++ }); ++ }, ++ // resolver: providers.github.resolvers.usernameMatchingUserEntityName(), ++ }, ++ }), ++ }, + }); +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +index 7ce5fcf..a12fee2 100644 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ b/packages/backend/src/plugins/scaffolder.ts +@@ -18,2 +18,4 @@ export default async function createPlugin( + catalogClient, ++ identity: env.identity, ++ permissions: env.permissions, + }); +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +index 27c42fc..e9469dc 100644 +--- a/packages/backend/src/plugins/search.ts ++++ b/packages/backend/src/plugins/search.ts +@@ -10,3 +10,2 @@ import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backe + import { Router } from 'express'; +-import { Duration } from 'luxon'; + +@@ -25,7 +24,7 @@ export default async function createPlugin( + const schedule = env.scheduler.createScheduledTaskRunner({ +- frequency: Duration.fromObject({ minutes: 10 }), +- timeout: Duration.fromObject({ minutes: 15 }), ++ frequency: { minutes: 10 }, ++ timeout: { minutes: 15 }, + // A 3 second delay gives the backend server a chance to initialize before + // any collators are executed, which may attempt requests against the API. +- initialDelay: Duration.fromObject({ seconds: 3 }), ++ initialDelay: { seconds: 3 }, + }); +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +index 8e0a864..9cd2c74 100644 +--- a/packages/backend/src/types.ts ++++ b/packages/backend/src/types.ts +@@ -11,2 +11,3 @@ import { PluginTaskScheduler } from '@backstage/backend-tasks'; + import { PermissionEvaluator } from '@backstage/plugin-permission-common'; ++import { IdentityApi } from '@backstage/plugin-auth-node'; + +@@ -22,2 +23,3 @@ export type PluginEnvironment = { + permissions: PermissionEvaluator; ++ identity: IdentityApi; + }; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..37c7fb1 +--- /dev/null ++++ b/playwright.config.ts +@@ -0,0 +1,60 @@ ++/* ++ * Copyright 2023 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { defineConfig } from '@playwright/test'; ++import { generateProjects } from '@backstage/e2e-test-utils/playwright'; ++ ++/** ++ * See https://playwright.dev/docs/test-configuration. ++ */ ++export default defineConfig({ ++ timeout: 60_000, ++ ++ expect: { ++ timeout: 5_000, ++ }, ++ ++ // Run your local dev server before starting the tests ++ webServer: process.env.CI ++ ? [] ++ : [ ++ { ++ command: 'yarn start', ++ port: 3000, ++ reuseExistingServer: true, ++ timeout: 60_000, ++ }, ++ ], ++ ++ forbidOnly: !!process.env.CI, ++ ++ retries: process.env.CI ? 2 : 0, ++ ++ reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]], ++ ++ use: { ++ actionTimeout: 0, ++ baseURL: ++ process.env.PLAYWRIGHT_URL ?? ++ (process.env.CI ? 'http://localhost:7007' : 'http://localhost:3000'), ++ screenshot: 'only-on-failure', ++ trace: 'on-first-retry', ++ }, ++ ++ outputDir: 'node_modules/.cache/e2e-test-results', ++ ++ projects: generateProjects(), // Find all packages with e2e-test folders ++}); +diff --git a/plugins/README.md b/plugins/README.md +index 58dc32c..d7865fd 100644 +--- a/plugins/README.md ++++ b/plugins/README.md +@@ -6,3 +6,3 @@ separate folder of its own. + If you want to create a new plugin here, go to your project root directory, run +-the command `yarn backstage-cli create`, and follow the on-screen instructions. ++the command `yarn new`, and follow the on-screen instructions. + +diff --git a/yarn.lock b/yarn.lock +new file mode 100644 +index 0000000..5ad7fe2 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1 @@ ++# intentionally left empty diff --git a/diffs/0.4.27..0.5.7-next.1.diff b/diffs/0.4.27..0.5.7-next.1.diff new file mode 100644 index 00000000..764be5e0 --- /dev/null +++ b/diffs/0.4.27..0.5.7-next.1.diff @@ -0,0 +1,1068 @@ +diff --git a/.dockerignore b/.dockerignore +index 505a7b5..05edb62 100644 +--- a/.dockerignore ++++ b/.dockerignore +@@ -1,2 +1,4 @@ + .git ++.yarn/cache ++.yarn/install-state.gz + node_modules +diff --git a/.eslintignore b/.eslintignore +new file mode 100644 +index 0000000..e5b1994 +--- /dev/null ++++ b/.eslintignore +@@ -0,0 +1 @@ ++playwright.config.ts +diff --git a/.gitignore b/.gitignore +index d16a8d3..fbf8139 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -17,2 +17,11 @@ node_modules/ + ++# Yarn 3 files ++.pnp.* ++.yarn/* ++!.yarn/patches ++!.yarn/plugins ++!.yarn/releases ++!.yarn/sdks ++!.yarn/versions ++ + # Node version directives +@@ -39 +48,7 @@ site + *-credentials.yaml ++ ++# vscode database functionality support files ++*.session.sql ++ ++# E2E test reports ++e2e-test-report/ +diff --git a/app-config.production.yaml b/app-config.production.yaml +index b9dd00b..8f0751c 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -11,10 +11,5 @@ backend: + baseUrl: http://localhost:7007 +- listen: +- port: 7007 +- # The following host directive binds to all IPv4 interfaces when its value +- # is "0.0.0.0". This is the most permissive setting. The right value depends +- # on your specific deployment. If you remove the host line entirely, the +- # backend will bind on the interface that corresponds to the backend.baseUrl +- # hostname. +- host: 0.0.0.0 ++ # The listener can also be expressed as a single : string. In this case we bind to ++ # all interfaces, the most permissive setting. The right value depends on your specific deployment. ++ listen: ':7007' + +@@ -34 +29,7 @@ backend: + # $file: /ca/server.crt ++ ++catalog: ++ # Overrides the default list locations from app-config.yaml as these contain example data. ++ # See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details ++ # on how to get entities into the catalog. ++ locations: [] +diff --git a/app-config.yaml b/app-config.yaml +index cd6998d..08d2abf 100644 +--- a/app-config.yaml ++++ b/app-config.yaml +@@ -9,3 +9,3 @@ backend: + # Used for enabling authentication, secret is shared by all backend plugins +- # See https://backstage.io/docs/tutorials/backend-to-backend-auth for ++ # See https://backstage.io/docs/auth/service-to-service-auth for + # information on the format +@@ -17,5 +17,4 @@ backend: + port: 7007 +- # Uncomment the following host directive to bind to all IPv4 interfaces and +- # not just the baseUrl hostname. +- # host: 0.0.0.0 ++ # Uncomment the following host directive to bind to specific interfaces ++ # host: 127.0.0.1 + csp: +@@ -26,5 +25,5 @@ backend: + origin: http://localhost:3000 +- methods: [GET, POST, PUT, DELETE] ++ methods: [GET, HEAD, PATCH, POST, PUT, DELETE] + credentials: true +- # This is for local developement only, it is not recommended to use this in production ++ # This is for local development only, it is not recommended to use this in production + # The production database configuration is stored in app-config.production.yaml +@@ -33,4 +32,2 @@ backend: + connection: ':memory:' +- cache: +- store: memory + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir +@@ -49,5 +46,8 @@ integrations: + proxy: +- '/test': +- target: 'https://example.com' +- changeOrigin: true ++ ### Example for how to add a proxy endpoint for the frontend. ++ ### A typical reason to do this is to handle HTTPS and CORS for internal services. ++ # endpoints: ++ # '/test': ++ # target: 'https://example.com' ++ # changeOrigin: true + +diff --git a/backstage.json b/backstage.json +index a15e91f..707af29 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "1.2.0" ++ "version": "1.20.0-next.1" + } +diff --git a/examples/template/template.yaml b/examples/template/template.yaml +index 50052b7..33f262b 100644 +--- a/examples/template/template.yaml ++++ b/examples/template/template.yaml +@@ -63,3 +63,3 @@ spec: + input: +- repoContentsUrl: ${{ steps.publish.output.repoContentsUrl }} ++ repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} + catalogInfoPath: '/catalog-info.yaml' +@@ -70,5 +70,5 @@ spec: + - title: Repository +- url: ${{ steps.publish.output.remoteUrl }} ++ url: ${{ steps['publish'].output.remoteUrl }} + - title: Open in catalog + icon: catalog +- entityRef: ${{ steps.register.output.entityRef }} ++ entityRef: ${{ steps['register'].output.entityRef }} +diff --git a/lerna.json b/lerna.json +index 322929d..529a62f 100644 +--- a/lerna.json ++++ b/lerna.json +@@ -3,4 +3,4 @@ + "npmClient": "yarn", +- "useWorkspaces": true, +- "version": "0.1.0" ++ "version": "0.1.0", ++ "$schema": "node_modules/lerna/schemas/lerna-schema.json" + } +diff --git a/package.json b/package.json +index 5a3196b..2364da8 100644 +--- a/package.json ++++ b/package.json +@@ -5,3 +5,3 @@ + "engines": { +- "node": "14 || 16" ++ "node": "18 || 20" + }, +@@ -11,3 +11,4 @@ + "start-backend": "yarn workspace backend start", +- "build": "backstage-cli repo build --all", ++ "build:backend": "yarn workspace backend build", ++ "build:all": "backstage-cli repo build --all", + "build-image": "yarn workspace backend build-image", +@@ -15,6 +16,7 @@ + "tsc:full": "tsc --skipLibCheck false --incremental false", +- "clean": "backstage-cli clean && lerna run clean", +- "diff": "lerna run diff --", +- "test": "backstage-cli test", +- "test:all": "lerna run test -- --coverage", ++ "clean": "backstage-cli repo clean", ++ "test": "backstage-cli repo test", ++ "test:all": "backstage-cli repo test --coverage", ++ "test:e2e": "playwright test", ++ "fix": "backstage-cli repo fix", + "lint": "backstage-cli repo lint --since origin/master", +@@ -22,4 +24,3 @@ + "prettier:check": "prettier --check .", +- "create-plugin": "backstage-cli create-plugin --scope internal", +- "remove-plugin": "backstage-cli remove-plugin" ++ "new": "backstage-cli new --scope internal" + }, +@@ -32,8 +33,11 @@ + "devDependencies": { +- "@backstage/cli": "^0.17.1", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/e2e-test-utils": "^0.1.0", ++ "@playwright/test": "^1.32.3", + "@spotify/prettier-config": "^12.0.0", +- "concurrently": "^6.0.0", +- "lerna": "^4.0.0", ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^9.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.6.4" ++ "typescript": "~5.2.0" + }, +diff --git a/packages/app/.eslintignore b/packages/app/.eslintignore +new file mode 100644 +index 0000000..a48cf0d +--- /dev/null ++++ b/packages/app/.eslintignore +@@ -0,0 +1 @@ ++public +diff --git a/packages/app/cypress.json b/packages/app/cypress.json +deleted file mode 100644 +index 5de7ebf..0000000 +--- a/packages/app/cypress.json ++++ /dev/null +@@ -1,5 +0,0 @@ +-{ +- "baseUrl": "http://localhost:3001", +- "fixturesFolder": false, +- "pluginsFile": false +-} +diff --git a/packages/app/cypress/.eslintrc.json b/packages/app/cypress/.eslintrc.json +deleted file mode 100644 +index 2b3a458..0000000 +--- a/packages/app/cypress/.eslintrc.json ++++ /dev/null +@@ -1,21 +0,0 @@ +-{ +- "plugins": ["cypress"], +- "extends": ["plugin:cypress/recommended"], +- "rules": { +- "jest/expect-expect": [ +- "error", +- { +- "assertFunctionNames": ["expect", "cy.contains"] +- } +- ], +- "import/no-extraneous-dependencies": [ +- "error", +- { +- "devDependencies": true, +- "optionalDependencies": true, +- "peerDependencies": true, +- "bundledDependencies": true +- } +- ] +- } +-} +diff --git a/packages/app/cypress/integration/app.js b/packages/app/cypress/integration/app.js +deleted file mode 100644 +index 43fb2e3..0000000 +--- a/packages/app/cypress/integration/app.js ++++ /dev/null +@@ -1,6 +0,0 @@ +-describe('App', () => { +- it('should render the catalog', () => { +- cy.visit('/'); +- cy.contains('My Company Catalog'); +- }); +-}); +diff --git a/packages/app/e2e-tests/app.test.ts b/packages/app/e2e-tests/app.test.ts +new file mode 100644 +index 0000000..d45bc0d +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,23 @@ ++/* ++ * Copyright 2020 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { test, expect } from '@playwright/test'; ++ ++test('App should render the welcome page', async ({ page }) => { ++ await page.goto('/'); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 7f04bae..2100405 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -8,28 +8,35 @@ + }, ++ "scripts": { ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "clean": "backstage-cli package clean", ++ "test": "backstage-cli package test", ++ "lint": "backstage-cli package lint" ++ }, + "dependencies": { +- "@backstage/app-defaults": "^1.0.2", +- "@backstage/catalog-model": "^1.0.2", +- "@backstage/cli": "^0.17.1", +- "@backstage/core-app-api": "^1.0.2", +- "@backstage/core-components": "^0.9.4", +- "@backstage/core-plugin-api": "^1.0.2", +- "@backstage/integration-react": "^1.1.0", +- "@backstage/plugin-api-docs": "^0.8.5", +- "@backstage/plugin-catalog": "^1.2.0", +- "@backstage/plugin-catalog-common": "^1.0.2", +- "@backstage/plugin-catalog-graph": "^0.2.17", +- "@backstage/plugin-catalog-import": "^0.8.8", +- "@backstage/plugin-catalog-react": "^1.1.0", +- "@backstage/plugin-github-actions": "^0.5.5", +- "@backstage/plugin-org": "^0.5.5", +- "@backstage/plugin-permission-react": "^0.4.1", +- "@backstage/plugin-scaffolder": "^1.2.0", +- "@backstage/plugin-search": "^0.8.1", +- "@backstage/plugin-search-react": "^0.2.0", +- "@backstage/plugin-tech-radar": "^0.5.12", +- "@backstage/plugin-techdocs": "^1.1.1", +- "@backstage/plugin-techdocs-react": "^1.0.0", +- "@backstage/plugin-techdocs-module-addons-contrib": "^1.0.0", +- "@backstage/plugin-user-settings": "^0.4.4", +- "@backstage/theme": "^0.2.15", ++ "@backstage/app-defaults": "^1.4.5-next.1", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/core-app-api": "^1.11.1-next.0", ++ "@backstage/core-components": "^0.13.8-next.1", ++ "@backstage/core-plugin-api": "^1.8.0-next.0", ++ "@backstage/integration-react": "^1.1.21-next.1", ++ "@backstage/plugin-api-docs": "^0.9.13-next.1", ++ "@backstage/plugin-catalog": "^1.15.0-next.1", ++ "@backstage/plugin-catalog-common": "^1.0.17", ++ "@backstage/plugin-catalog-graph": "^0.2.38-next.1", ++ "@backstage/plugin-catalog-import": "^0.10.2-next.1", ++ "@backstage/plugin-catalog-react": "^1.9.0-next.1", ++ "@backstage/plugin-github-actions": "^0.6.7-next.1", ++ "@backstage/plugin-org": "^0.6.16-next.1", ++ "@backstage/plugin-permission-react": "^0.4.17-next.0", ++ "@backstage/plugin-scaffolder": "^1.16.0-next.1", ++ "@backstage/plugin-search": "^1.4.2-next.1", ++ "@backstage/plugin-search-react": "^1.7.2-next.1", ++ "@backstage/plugin-tech-radar": "^0.6.10-next.1", ++ "@backstage/plugin-techdocs": "^1.8.1-next.1", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.2-next.1", ++ "@backstage/plugin-techdocs-react": "^1.1.13-next.1", ++ "@backstage/plugin-user-settings": "^0.7.12-next.1", ++ "@backstage/theme": "^0.4.4-next.0", + "@material-ui/core": "^4.12.2", +@@ -39,29 +46,15 @@ + "react-dom": "^17.0.2", +- "react-router": "6.0.0-beta.0", +- "react-router-dom": "6.0.0-beta.0", +- "react-use": "^15.3.3" ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^1.1.0", ++ "@backstage/test-utils": "^1.4.5-next.0", ++ "@playwright/test": "^1.32.3", + "@testing-library/jest-dom": "^5.10.1", + "@testing-library/react": "^12.1.3", +- "@testing-library/user-event": "^12.0.7", +- "@types/jest": "^26.0.7", +- "@types/node": "^14.14.32", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^8.0.0", + "@types/react-dom": "*", +- "cross-env": "^7.0.0", +- "cypress": "^7.3.0", +- "eslint-plugin-cypress": "^2.10.3", +- "start-server-and-test": "^1.10.11" +- }, +- "scripts": { +- "start": "backstage-cli package start", +- "build": "backstage-cli package build", +- "clean": "backstage-cli package clean", +- "test": "backstage-cli package test", +- "lint": "backstage-cli package lint", +- "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", +- "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", +- "cy:dev": "cypress open", +- "cy:run": "cypress run" ++ "cross-env": "^7.0.0" + }, +diff --git a/packages/app/public/index.html b/packages/app/public/index.html +index a936c73..fc758ee 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -10,3 +10,2 @@ + /> +- + +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index c487726..8d62f29 100644 +--- a/packages/app/src/App.tsx ++++ b/packages/app/src/App.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { Navigate, Route } from 'react-router'; ++import { Navigate, Route } from 'react-router-dom'; + import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; +@@ -31,5 +31,5 @@ import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; + import { createApp } from '@backstage/app-defaults'; +-import { FlatRoutes } from '@backstage/core-app-api'; ++import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; + import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; +-import { PermissionedRoute } from '@backstage/plugin-permission-react'; ++import { RequirePermission } from '@backstage/plugin-permission-react'; + import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; +@@ -42,2 +42,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -48,2 +49,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -55,8 +57,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -83,6 +82,9 @@ const routes = ( + /> +- } ++ element={ ++ ++ ++ ++ } + /> +@@ -96,4 +98,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -103,5 +105,3 @@ const App = () => ( + +- ++ , + ); +- +-export default App; +diff --git a/packages/app/src/components/Root/LogoFull.tsx b/packages/app/src/components/Root/LogoFull.tsx +index c7b1c84..47e3b73 100644 +--- a/packages/app/src/components/Root/LogoFull.tsx ++++ b/packages/app/src/components/Root/LogoFull.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/LogoIcon.tsx b/packages/app/src/components/Root/LogoIcon.tsx +index 073cf6e..7eae8c7 100644 +--- a/packages/app/src/components/Root/LogoIcon.tsx ++++ b/packages/app/src/components/Root/LogoIcon.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx +index 90738c4..6768b48 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,19 +1,3 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- +-import React, { useContext, PropsWithChildren } from 'react'; +-import { Link, makeStyles } from '@material-ui/core'; ++import React, { PropsWithChildren } from 'react'; ++import { makeStyles } from '@material-ui/core'; + import HomeIcon from '@material-ui/icons/Home'; +@@ -25,3 +9,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -34,3 +17,2 @@ import { + sidebarConfig, +- SidebarContext, + SidebarDivider, +@@ -41,2 +23,4 @@ import { + SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -62,3 +46,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -66,8 +50,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +diff --git a/packages/app/src/components/Root/index.ts b/packages/app/src/components/Root/index.ts +index dff706f..3528881 100644 +--- a/packages/app/src/components/Root/index.ts ++++ b/packages/app/src/components/Root/index.ts +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + export { Root } from './Root'; +diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx +index 84d0944..6722ea2 100644 +--- a/packages/app/src/components/catalog/EntityPage.tsx ++++ b/packages/app/src/components/catalog/EntityPage.tsx +@@ -1,16 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ + import React from 'react'; +@@ -42,2 +27,4 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +@@ -70,2 +57,13 @@ import { + ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); ++ + const cicdContent = ( +@@ -107,2 +105,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -169,3 +175,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -196,3 +202,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -215,3 +221,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -315,3 +321,6 @@ const systemPage = ( + +- ++ ++ ++ ++ + +diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx +index d4c7c92..1788dde 100644 +--- a/packages/app/src/components/search/SearchPage.tsx ++++ b/packages/app/src/components/search/SearchPage.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; ++import { makeStyles, Theme, Grid, Paper } from '@material-ui/core'; + +@@ -10,2 +10,3 @@ import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; + ++import { SearchType } from '@backstage/plugin-search'; + import { +@@ -14,6 +15,5 @@ import { + SearchResult, +- SearchType, +- DefaultResultListItem, +-} from '@backstage/plugin-search'; +-import { useSearch } from '@backstage/plugin-search-react'; ++ SearchPagination, ++ useSearch, ++} from '@backstage/plugin-search-react'; + import { +@@ -111,35 +111,6 @@ const SearchPage = () => { + ++ + +- {({ results }) => ( +- +- {results.map(({ type, document, highlight }) => { +- switch (type) { +- case 'software-catalog': +- return ( +- +- ); +- case 'techdocs': +- return ( +- +- ); +- default: +- return ( +- +- ); +- } +- })} +- +- )} ++ } /> ++ } /> + +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index a5773aa..18548e9 100644 +--- a/packages/backend/Dockerfile ++++ b/packages/backend/Dockerfile +@@ -7,3 +7,3 @@ + # yarn tsc +-# yarn build ++# yarn build:backend + # +@@ -11,11 +11,28 @@ + +-FROM node:16-bullseye-slim ++FROM node:18-bookworm-slim + ++# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends python3 g++ build-essential && \ ++ yarn config set python /usr/bin/python3 ++ ++# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, ++# in which case you should also move better-sqlite3 to "devDependencies" in package.json. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends libsqlite3-dev ++ ++# From here on we use the least-privileged `node` user to run the backend. ++USER node ++ ++# This should create the app dir as `node`. ++# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. ++# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. + WORKDIR /app + +-# install sqlite3 dependencies, you can skip this if you don't use sqlite3 in the image +-RUN apt-get update && \ +- apt-get install -y --no-install-recommends libsqlite3-dev python3 build-essential && \ +- rm -rf /var/lib/apt/lists/* && \ +- yarn config set python /usr/bin/python3 ++# This switches many Node.js dependencies to production mode. ++ENV NODE_ENV production + +@@ -24,9 +41,10 @@ RUN apt-get update && \ + # and along with yarn.lock and the root package.json, that's enough to run yarn install. +-COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ ++COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ + RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz + +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + + # Then copy the rest of the backend bundle, along with any other files we might want. +-COPY packages/backend/dist/bundle.tar.gz app-config*.yaml ./ ++COPY --chown=node:node packages/backend/dist/bundle.tar.gz app-config*.yaml ./ + RUN tar xzf bundle.tar.gz && rm bundle.tar.gz +diff --git a/packages/backend/README.md b/packages/backend/README.md +index 02426ef..3607b0a 100644 +--- a/packages/backend/README.md ++++ b/packages/backend/README.md +@@ -18,4 +18,2 @@ To run the example backend, first go to the project root and run + yarn install +-yarn tsc +-yarn build + ``` +@@ -27,7 +25,2 @@ After that, go to the `packages/backend` directory and run + ```bash +-AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x \ +-AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x \ +-AUTH_OAUTH2_CLIENT_ID=x AUTH_OAUTH2_CLIENT_SECRET=x \ +-AUTH_OAUTH2_AUTH_URL=x AUTH_OAUTH2_TOKEN_URL=x \ +-LOG_LEVEL=debug \ + yarn start +@@ -35,4 +28,4 @@ yarn start + +-Substitute `x` for actual values, or leave them as dummy values just to try out +-the backend without using the auth or sentry features. ++If you want to override any configuration locally, for example adding any secrets, ++you can do so in `app-config.local.yaml`. + +@@ -45,3 +38,3 @@ locations to the backend. These are places where the backend can find some + entity descriptor data to consume and serve. For more information, see +-[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog). ++[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). + +@@ -65,2 +58,2 @@ and + - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +-- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) ++- [Backstage Documentation](https://backstage.io/docs) +diff --git a/packages/backend/package.json b/packages/backend/package.json +index 134ddf4..f224eaf 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -18,19 +18,21 @@ + "dependencies": { ++ "@backstage/backend-common": "^0.19.9-next.1", ++ "@backstage/backend-tasks": "^0.5.12-next.1", ++ "@backstage/catalog-client": "^1.4.5", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/config": "^1.1.1", ++ "@backstage/plugin-app-backend": "^0.3.55-next.1", ++ "@backstage/plugin-auth-backend": "^0.20.0-next.1", ++ "@backstage/plugin-auth-node": "^0.4.1-next.1", ++ "@backstage/plugin-catalog-backend": "^1.15.0-next.1", ++ "@backstage/plugin-permission-common": "^0.7.9", ++ "@backstage/plugin-permission-node": "^0.7.18-next.1", ++ "@backstage/plugin-proxy-backend": "^0.4.5-next.1", ++ "@backstage/plugin-scaffolder-backend": "^1.19.0-next.1", ++ "@backstage/plugin-search-backend": "^1.4.7-next.1", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.16-next.1", ++ "@backstage/plugin-search-backend-node": "^1.2.11-next.1", ++ "@backstage/plugin-techdocs-backend": "^1.9.0-next.1", + "app": "link:../app", +- "@backstage/backend-common": "^0.13.3", +- "@backstage/backend-tasks": "^0.3.1", +- "@backstage/catalog-model": "^1.0.2", +- "@backstage/catalog-client": "^1.0.2", +- "@backstage/config": "^1.0.1", +- "@backstage/plugin-app-backend": "^0.3.32", +- "@backstage/plugin-auth-backend": "^0.14.0", +- "@backstage/plugin-catalog-backend": "^1.1.2", +- "@backstage/plugin-permission-common": "^0.6.1", +- "@backstage/plugin-permission-node": "^0.6.1", +- "@backstage/plugin-proxy-backend": "^0.2.26", +- "@backstage/plugin-scaffolder-backend": "^1.2.0", +- "@backstage/plugin-search-backend": "^0.5.2", +- "@backstage/plugin-search-backend-module-pg": "^0.3.3", +- "@backstage/plugin-search-backend-node": "^0.6.1", +- "@backstage/plugin-techdocs-backend": "^1.1.1", ++ "better-sqlite3": "^8.0.0", + "dockerode": "^3.3.1", +@@ -42,8 +44,7 @@ + "devDependencies": { +- "@backstage/cli": "^0.17.1", ++ "@backstage/cli": "^0.24.0-next.1", + "@types/dockerode": "^3.3.0", +- "@types/express-serve-static-core": "^4.17.5", + "@types/express": "^4.17.6", +- "@types/luxon": "^2.0.4", +- "better-sqlite3": "^7.5.0" ++ "@types/express-serve-static-core": "^4.17.5", ++ "@types/luxon": "^2.0.4" + }, +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 70bc66b..04c4ff9 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -17,3 +17,3 @@ import { + DatabaseManager, +- SingleHostDiscovery, ++ HostDiscovery, + UrlReaders, +@@ -32,2 +32,3 @@ import { PluginEnvironment } from './types'; + import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; + +@@ -36,7 +37,11 @@ function makeCreateEnv(config: Config) { + const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++ const discovery = HostDiscovery.fromConfig(config); + const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++ const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); + const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); ++ const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); ++ ++ const identity = DefaultIdentityClient.create({ ++ discovery, ++ }); + const permissions = ServerPermissionClient.fromConfig(config, { +@@ -63,2 +68,3 @@ function makeCreateEnv(config: Config) { + permissions, ++ identity, + }; +@@ -106,3 +112,3 @@ module.hot?.accept(); + main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); ++ console.error('Backend failed to start up', error); + process.exit(1); +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +index 13f18c5..77eb6aa 100644 +--- a/packages/backend/src/plugins/auth.ts ++++ b/packages/backend/src/plugins/auth.ts +@@ -20,8 +20,17 @@ export default async function createPlugin( + +- // This overrides the default GitHub auth provider with a custom one. +- // Since the options are empty it will behave just like the default +- // provider, but if you uncomment the `signIn` section you will enable +- // sign-in via GitHub. This particular configuration uses a resolver +- // that matches the username to the user entity name. See the auth +- // documentation for more details on how to enable and customize sign-in: ++ // This replaces the default GitHub auth provider with a customized one. ++ // The `signIn` option enables sign-in for this provider, using the ++ // identity resolution logic that's provided in the `resolver` callback. ++ // ++ // This particular resolver makes all users share a single "guest" identity. ++ // It should only be used for testing and trying out Backstage. ++ // ++ // If you want to use a production ready resolver you can switch to ++ // the one that is commented out below, it looks up a user entity in the ++ // catalog using the GitHub username of the authenticated user. ++ // That resolver requires you to have user entities populated in the catalog, ++ // for example using https://backstage.io/docs/integrations/github/org ++ // ++ // There are other resolvers to choose from, and you can also create ++ // your own, see the auth documentation for more details: + // +@@ -29,5 +38,14 @@ export default async function createPlugin( + github: providers.github.create({ +- // signIn: { +- // resolver: providers.github.resolvers.usernameMatchingUserEntityName(), +- // }, ++ signIn: { ++ resolver(_, ctx) { ++ const userRef = 'user:default/guest'; // Must be a full entity reference ++ return ctx.issueToken({ ++ claims: { ++ sub: userRef, // The user's own identity ++ ent: [userRef], // A list of identities that the user claims ownership through ++ }, ++ }); ++ }, ++ // resolver: providers.github.resolvers.usernameMatchingUserEntityName(), ++ }, + }), +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +index 7ce5fcf..a12fee2 100644 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ b/packages/backend/src/plugins/scaffolder.ts +@@ -18,2 +18,4 @@ export default async function createPlugin( + catalogClient, ++ identity: env.identity, ++ permissions: env.permissions, + }); +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +index 8e0a864..9cd2c74 100644 +--- a/packages/backend/src/types.ts ++++ b/packages/backend/src/types.ts +@@ -11,2 +11,3 @@ import { PluginTaskScheduler } from '@backstage/backend-tasks'; + import { PermissionEvaluator } from '@backstage/plugin-permission-common'; ++import { IdentityApi } from '@backstage/plugin-auth-node'; + +@@ -22,2 +23,3 @@ export type PluginEnvironment = { + permissions: PermissionEvaluator; ++ identity: IdentityApi; + }; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..37c7fb1 +--- /dev/null ++++ b/playwright.config.ts +@@ -0,0 +1,60 @@ ++/* ++ * Copyright 2023 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { defineConfig } from '@playwright/test'; ++import { generateProjects } from '@backstage/e2e-test-utils/playwright'; ++ ++/** ++ * See https://playwright.dev/docs/test-configuration. ++ */ ++export default defineConfig({ ++ timeout: 60_000, ++ ++ expect: { ++ timeout: 5_000, ++ }, ++ ++ // Run your local dev server before starting the tests ++ webServer: process.env.CI ++ ? [] ++ : [ ++ { ++ command: 'yarn start', ++ port: 3000, ++ reuseExistingServer: true, ++ timeout: 60_000, ++ }, ++ ], ++ ++ forbidOnly: !!process.env.CI, ++ ++ retries: process.env.CI ? 2 : 0, ++ ++ reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]], ++ ++ use: { ++ actionTimeout: 0, ++ baseURL: ++ process.env.PLAYWRIGHT_URL ?? ++ (process.env.CI ? 'http://localhost:7007' : 'http://localhost:3000'), ++ screenshot: 'only-on-failure', ++ trace: 'on-first-retry', ++ }, ++ ++ outputDir: 'node_modules/.cache/e2e-test-results', ++ ++ projects: generateProjects(), // Find all packages with e2e-test folders ++}); +diff --git a/plugins/README.md b/plugins/README.md +index 58dc32c..d7865fd 100644 +--- a/plugins/README.md ++++ b/plugins/README.md +@@ -6,3 +6,3 @@ separate folder of its own. + If you want to create a new plugin here, go to your project root directory, run +-the command `yarn backstage-cli create`, and follow the on-screen instructions. ++the command `yarn new`, and follow the on-screen instructions. + +diff --git a/yarn.lock b/yarn.lock +new file mode 100644 +index 0000000..5ad7fe2 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1 @@ ++# intentionally left empty diff --git a/diffs/0.4.28..0.5.7-next.1.diff b/diffs/0.4.28..0.5.7-next.1.diff new file mode 100644 index 00000000..35cec013 --- /dev/null +++ b/diffs/0.4.28..0.5.7-next.1.diff @@ -0,0 +1,987 @@ +diff --git a/.dockerignore b/.dockerignore +index 505a7b5..05edb62 100644 +--- a/.dockerignore ++++ b/.dockerignore +@@ -1,2 +1,4 @@ + .git ++.yarn/cache ++.yarn/install-state.gz + node_modules +diff --git a/.eslintignore b/.eslintignore +new file mode 100644 +index 0000000..e5b1994 +--- /dev/null ++++ b/.eslintignore +@@ -0,0 +1 @@ ++playwright.config.ts +diff --git a/.gitignore b/.gitignore +index d16a8d3..fbf8139 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -17,2 +17,11 @@ node_modules/ + ++# Yarn 3 files ++.pnp.* ++.yarn/* ++!.yarn/patches ++!.yarn/plugins ++!.yarn/releases ++!.yarn/sdks ++!.yarn/versions ++ + # Node version directives +@@ -39 +48,7 @@ site + *-credentials.yaml ++ ++# vscode database functionality support files ++*.session.sql ++ ++# E2E test reports ++e2e-test-report/ +diff --git a/app-config.production.yaml b/app-config.production.yaml +index 6535d96..8f0751c 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -11,10 +11,5 @@ backend: + baseUrl: http://localhost:7007 +- listen: +- port: 7007 +- # The following host directive binds to all IPv4 interfaces when its value +- # is "0.0.0.0". This is the most permissive setting. The right value depends +- # on your specific deployment. If you remove the host line entirely, the +- # backend will bind on the interface that corresponds to the backend.baseUrl +- # hostname. +- host: 0.0.0.0 ++ # The listener can also be expressed as a single : string. In this case we bind to ++ # all interfaces, the most permissive setting. The right value depends on your specific deployment. ++ listen: ':7007' + +@@ -37,3 +32,3 @@ catalog: + # Overrides the default list locations from app-config.yaml as these contain example data. +- # See https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog for more details ++ # See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details + # on how to get entities into the catalog. +diff --git a/app-config.yaml b/app-config.yaml +index cd6998d..08d2abf 100644 +--- a/app-config.yaml ++++ b/app-config.yaml +@@ -9,3 +9,3 @@ backend: + # Used for enabling authentication, secret is shared by all backend plugins +- # See https://backstage.io/docs/tutorials/backend-to-backend-auth for ++ # See https://backstage.io/docs/auth/service-to-service-auth for + # information on the format +@@ -17,5 +17,4 @@ backend: + port: 7007 +- # Uncomment the following host directive to bind to all IPv4 interfaces and +- # not just the baseUrl hostname. +- # host: 0.0.0.0 ++ # Uncomment the following host directive to bind to specific interfaces ++ # host: 127.0.0.1 + csp: +@@ -26,5 +25,5 @@ backend: + origin: http://localhost:3000 +- methods: [GET, POST, PUT, DELETE] ++ methods: [GET, HEAD, PATCH, POST, PUT, DELETE] + credentials: true +- # This is for local developement only, it is not recommended to use this in production ++ # This is for local development only, it is not recommended to use this in production + # The production database configuration is stored in app-config.production.yaml +@@ -33,4 +32,2 @@ backend: + connection: ':memory:' +- cache: +- store: memory + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir +@@ -49,5 +46,8 @@ integrations: + proxy: +- '/test': +- target: 'https://example.com' +- changeOrigin: true ++ ### Example for how to add a proxy endpoint for the frontend. ++ ### A typical reason to do this is to handle HTTPS and CORS for internal services. ++ # endpoints: ++ # '/test': ++ # target: 'https://example.com' ++ # changeOrigin: true + +diff --git a/backstage.json b/backstage.json +index b23367d..707af29 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "1.3.0" ++ "version": "1.20.0-next.1" + } +diff --git a/examples/template/template.yaml b/examples/template/template.yaml +index 50052b7..33f262b 100644 +--- a/examples/template/template.yaml ++++ b/examples/template/template.yaml +@@ -63,3 +63,3 @@ spec: + input: +- repoContentsUrl: ${{ steps.publish.output.repoContentsUrl }} ++ repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} + catalogInfoPath: '/catalog-info.yaml' +@@ -70,5 +70,5 @@ spec: + - title: Repository +- url: ${{ steps.publish.output.remoteUrl }} ++ url: ${{ steps['publish'].output.remoteUrl }} + - title: Open in catalog + icon: catalog +- entityRef: ${{ steps.register.output.entityRef }} ++ entityRef: ${{ steps['register'].output.entityRef }} +diff --git a/lerna.json b/lerna.json +index 322929d..529a62f 100644 +--- a/lerna.json ++++ b/lerna.json +@@ -3,4 +3,4 @@ + "npmClient": "yarn", +- "useWorkspaces": true, +- "version": "0.1.0" ++ "version": "0.1.0", ++ "$schema": "node_modules/lerna/schemas/lerna-schema.json" + } +diff --git a/package.json b/package.json +index bfc2fd7..2364da8 100644 +--- a/package.json ++++ b/package.json +@@ -5,3 +5,3 @@ + "engines": { +- "node": "14 || 16" ++ "node": "18 || 20" + }, +@@ -11,3 +11,4 @@ + "start-backend": "yarn workspace backend start", +- "build": "backstage-cli repo build --all", ++ "build:backend": "yarn workspace backend build", ++ "build:all": "backstage-cli repo build --all", + "build-image": "yarn workspace backend build-image", +@@ -15,6 +16,7 @@ + "tsc:full": "tsc --skipLibCheck false --incremental false", +- "clean": "backstage-cli clean && lerna run clean", +- "diff": "lerna run diff --", +- "test": "backstage-cli test", +- "test:all": "lerna run test -- --coverage", ++ "clean": "backstage-cli repo clean", ++ "test": "backstage-cli repo test", ++ "test:all": "backstage-cli repo test --coverage", ++ "test:e2e": "playwright test", ++ "fix": "backstage-cli repo fix", + "lint": "backstage-cli repo lint --since origin/master", +@@ -22,4 +24,3 @@ + "prettier:check": "prettier --check .", +- "create-plugin": "backstage-cli create-plugin --scope internal", +- "remove-plugin": "backstage-cli remove-plugin" ++ "new": "backstage-cli new --scope internal" + }, +@@ -32,8 +33,11 @@ + "devDependencies": { +- "@backstage/cli": "^0.17.2", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/e2e-test-utils": "^0.1.0", ++ "@playwright/test": "^1.32.3", + "@spotify/prettier-config": "^12.0.0", +- "concurrently": "^6.0.0", +- "lerna": "^4.0.0", ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^9.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.6.4" ++ "typescript": "~5.2.0" + }, +diff --git a/packages/app/.eslintignore b/packages/app/.eslintignore +new file mode 100644 +index 0000000..a48cf0d +--- /dev/null ++++ b/packages/app/.eslintignore +@@ -0,0 +1 @@ ++public +diff --git a/packages/app/cypress.json b/packages/app/cypress.json +deleted file mode 100644 +index 0cb845a..0000000 +--- a/packages/app/cypress.json ++++ /dev/null +@@ -1,6 +0,0 @@ +-{ +- "baseUrl": "http://localhost:3001", +- "fixturesFolder": false, +- "pluginsFile": false, +- "retries": 3 +-} +diff --git a/packages/app/cypress/.eslintrc.json b/packages/app/cypress/.eslintrc.json +deleted file mode 100644 +index 2b3a458..0000000 +--- a/packages/app/cypress/.eslintrc.json ++++ /dev/null +@@ -1,21 +0,0 @@ +-{ +- "plugins": ["cypress"], +- "extends": ["plugin:cypress/recommended"], +- "rules": { +- "jest/expect-expect": [ +- "error", +- { +- "assertFunctionNames": ["expect", "cy.contains"] +- } +- ], +- "import/no-extraneous-dependencies": [ +- "error", +- { +- "devDependencies": true, +- "optionalDependencies": true, +- "peerDependencies": true, +- "bundledDependencies": true +- } +- ] +- } +-} +diff --git a/packages/app/cypress/integration/app.js b/packages/app/cypress/integration/app.js +deleted file mode 100644 +index 43fb2e3..0000000 +--- a/packages/app/cypress/integration/app.js ++++ /dev/null +@@ -1,6 +0,0 @@ +-describe('App', () => { +- it('should render the catalog', () => { +- cy.visit('/'); +- cy.contains('My Company Catalog'); +- }); +-}); +diff --git a/packages/app/e2e-tests/app.test.ts b/packages/app/e2e-tests/app.test.ts +new file mode 100644 +index 0000000..d45bc0d +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,23 @@ ++/* ++ * Copyright 2020 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { test, expect } from '@playwright/test'; ++ ++test('App should render the welcome page', async ({ page }) => { ++ await page.goto('/'); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index f4cbcda..2100405 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -8,28 +8,35 @@ + }, ++ "scripts": { ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "clean": "backstage-cli package clean", ++ "test": "backstage-cli package test", ++ "lint": "backstage-cli package lint" ++ }, + "dependencies": { +- "@backstage/app-defaults": "^1.0.3", +- "@backstage/catalog-model": "^1.0.3", +- "@backstage/cli": "^0.17.2", +- "@backstage/core-app-api": "^1.0.3", +- "@backstage/core-components": "^0.9.5", +- "@backstage/core-plugin-api": "^1.0.3", +- "@backstage/integration-react": "^1.1.1", +- "@backstage/plugin-api-docs": "^0.8.6", +- "@backstage/plugin-catalog": "^1.3.0", +- "@backstage/plugin-catalog-common": "^1.0.3", +- "@backstage/plugin-catalog-graph": "^0.2.18", +- "@backstage/plugin-catalog-import": "^0.8.9", +- "@backstage/plugin-catalog-react": "^1.1.1", +- "@backstage/plugin-github-actions": "^0.5.6", +- "@backstage/plugin-org": "^0.5.6", +- "@backstage/plugin-permission-react": "^0.4.2", +- "@backstage/plugin-scaffolder": "^1.3.0", +- "@backstage/plugin-search": "^0.9.0", +- "@backstage/plugin-search-react": "^0.2.1", +- "@backstage/plugin-tech-radar": "^0.5.13", +- "@backstage/plugin-techdocs": "^1.2.0", +- "@backstage/plugin-techdocs-react": "^1.0.1", +- "@backstage/plugin-techdocs-module-addons-contrib": "^1.0.1", +- "@backstage/plugin-user-settings": "^0.4.5", +- "@backstage/theme": "^0.2.15", ++ "@backstage/app-defaults": "^1.4.5-next.1", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/core-app-api": "^1.11.1-next.0", ++ "@backstage/core-components": "^0.13.8-next.1", ++ "@backstage/core-plugin-api": "^1.8.0-next.0", ++ "@backstage/integration-react": "^1.1.21-next.1", ++ "@backstage/plugin-api-docs": "^0.9.13-next.1", ++ "@backstage/plugin-catalog": "^1.15.0-next.1", ++ "@backstage/plugin-catalog-common": "^1.0.17", ++ "@backstage/plugin-catalog-graph": "^0.2.38-next.1", ++ "@backstage/plugin-catalog-import": "^0.10.2-next.1", ++ "@backstage/plugin-catalog-react": "^1.9.0-next.1", ++ "@backstage/plugin-github-actions": "^0.6.7-next.1", ++ "@backstage/plugin-org": "^0.6.16-next.1", ++ "@backstage/plugin-permission-react": "^0.4.17-next.0", ++ "@backstage/plugin-scaffolder": "^1.16.0-next.1", ++ "@backstage/plugin-search": "^1.4.2-next.1", ++ "@backstage/plugin-search-react": "^1.7.2-next.1", ++ "@backstage/plugin-tech-radar": "^0.6.10-next.1", ++ "@backstage/plugin-techdocs": "^1.8.1-next.1", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.2-next.1", ++ "@backstage/plugin-techdocs-react": "^1.1.13-next.1", ++ "@backstage/plugin-user-settings": "^0.7.12-next.1", ++ "@backstage/theme": "^0.4.4-next.0", + "@material-ui/core": "^4.12.2", +@@ -39,29 +46,15 @@ + "react-dom": "^17.0.2", +- "react-router": "6.0.0-beta.0", +- "react-router-dom": "6.0.0-beta.0", +- "react-use": "^15.3.3" ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^1.1.1", ++ "@backstage/test-utils": "^1.4.5-next.0", ++ "@playwright/test": "^1.32.3", + "@testing-library/jest-dom": "^5.10.1", + "@testing-library/react": "^12.1.3", +- "@testing-library/user-event": "^12.0.7", +- "@types/jest": "^26.0.7", +- "@types/node": "^14.14.32", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^8.0.0", + "@types/react-dom": "*", +- "cross-env": "^7.0.0", +- "cypress": "^9.7.0", +- "eslint-plugin-cypress": "^2.10.3", +- "start-server-and-test": "^1.10.11" +- }, +- "scripts": { +- "start": "backstage-cli package start", +- "build": "backstage-cli package build", +- "clean": "backstage-cli package clean", +- "test": "backstage-cli package test", +- "lint": "backstage-cli package lint", +- "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", +- "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", +- "cy:dev": "cypress open", +- "cy:run": "cypress run --browser chrome" ++ "cross-env": "^7.0.0" + }, +diff --git a/packages/app/public/index.html b/packages/app/public/index.html +index a936c73..fc758ee 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -10,3 +10,2 @@ + /> +- + +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index c487726..8d62f29 100644 +--- a/packages/app/src/App.tsx ++++ b/packages/app/src/App.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { Navigate, Route } from 'react-router'; ++import { Navigate, Route } from 'react-router-dom'; + import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; +@@ -31,5 +31,5 @@ import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; + import { createApp } from '@backstage/app-defaults'; +-import { FlatRoutes } from '@backstage/core-app-api'; ++import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; + import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; +-import { PermissionedRoute } from '@backstage/plugin-permission-react'; ++import { RequirePermission } from '@backstage/plugin-permission-react'; + import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; +@@ -42,2 +42,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -48,2 +49,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -55,8 +57,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -83,6 +82,9 @@ const routes = ( + /> +- } ++ element={ ++ ++ ++ ++ } + /> +@@ -96,4 +98,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -103,5 +105,3 @@ const App = () => ( + +- ++ , + ); +- +-export default App; +diff --git a/packages/app/src/components/Root/LogoFull.tsx b/packages/app/src/components/Root/LogoFull.tsx +index c7b1c84..47e3b73 100644 +--- a/packages/app/src/components/Root/LogoFull.tsx ++++ b/packages/app/src/components/Root/LogoFull.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/LogoIcon.tsx b/packages/app/src/components/Root/LogoIcon.tsx +index 073cf6e..7eae8c7 100644 +--- a/packages/app/src/components/Root/LogoIcon.tsx ++++ b/packages/app/src/components/Root/LogoIcon.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx +index b1164a3..6768b48 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,19 +1,3 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React, { PropsWithChildren } from 'react'; +-import { Link, makeStyles } from '@material-ui/core'; ++import { makeStyles } from '@material-ui/core'; + import HomeIcon from '@material-ui/icons/Home'; +@@ -25,3 +9,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -41,2 +24,3 @@ import { + useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -66,9 +50,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +diff --git a/packages/app/src/components/Root/index.ts b/packages/app/src/components/Root/index.ts +index dff706f..3528881 100644 +--- a/packages/app/src/components/Root/index.ts ++++ b/packages/app/src/components/Root/index.ts +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + export { Root } from './Root'; +diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx +index 6ec4da0..6722ea2 100644 +--- a/packages/app/src/components/catalog/EntityPage.tsx ++++ b/packages/app/src/components/catalog/EntityPage.tsx +@@ -1,16 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ + import React from 'react'; +@@ -42,2 +27,4 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +@@ -118,2 +105,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -326,3 +321,6 @@ const systemPage = ( + +- ++ ++ ++ ++ + +diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx +index 928b820..1788dde 100644 +--- a/packages/app/src/components/search/SearchPage.tsx ++++ b/packages/app/src/components/search/SearchPage.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; ++import { makeStyles, Theme, Grid, Paper } from '@material-ui/core'; + +@@ -12,3 +12,2 @@ import { SearchType } from '@backstage/plugin-search'; + import { +- DefaultResultListItem, + SearchBar, +@@ -16,2 +15,3 @@ import { + SearchResult, ++ SearchPagination, + useSearch, +@@ -111,38 +111,6 @@ const SearchPage = () => { + ++ + +- {({ results }) => ( +- +- {results.map(({ type, document, highlight, rank }) => { +- switch (type) { +- case 'software-catalog': +- return ( +- +- ); +- case 'techdocs': +- return ( +- +- ); +- default: +- return ( +- +- ); +- } +- })} +- +- )} ++ } /> ++ } /> + +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index a5773aa..18548e9 100644 +--- a/packages/backend/Dockerfile ++++ b/packages/backend/Dockerfile +@@ -7,3 +7,3 @@ + # yarn tsc +-# yarn build ++# yarn build:backend + # +@@ -11,11 +11,28 @@ + +-FROM node:16-bullseye-slim ++FROM node:18-bookworm-slim + ++# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends python3 g++ build-essential && \ ++ yarn config set python /usr/bin/python3 ++ ++# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, ++# in which case you should also move better-sqlite3 to "devDependencies" in package.json. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends libsqlite3-dev ++ ++# From here on we use the least-privileged `node` user to run the backend. ++USER node ++ ++# This should create the app dir as `node`. ++# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. ++# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. + WORKDIR /app + +-# install sqlite3 dependencies, you can skip this if you don't use sqlite3 in the image +-RUN apt-get update && \ +- apt-get install -y --no-install-recommends libsqlite3-dev python3 build-essential && \ +- rm -rf /var/lib/apt/lists/* && \ +- yarn config set python /usr/bin/python3 ++# This switches many Node.js dependencies to production mode. ++ENV NODE_ENV production + +@@ -24,9 +41,10 @@ RUN apt-get update && \ + # and along with yarn.lock and the root package.json, that's enough to run yarn install. +-COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ ++COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ + RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz + +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + + # Then copy the rest of the backend bundle, along with any other files we might want. +-COPY packages/backend/dist/bundle.tar.gz app-config*.yaml ./ ++COPY --chown=node:node packages/backend/dist/bundle.tar.gz app-config*.yaml ./ + RUN tar xzf bundle.tar.gz && rm bundle.tar.gz +diff --git a/packages/backend/README.md b/packages/backend/README.md +index 02426ef..3607b0a 100644 +--- a/packages/backend/README.md ++++ b/packages/backend/README.md +@@ -18,4 +18,2 @@ To run the example backend, first go to the project root and run + yarn install +-yarn tsc +-yarn build + ``` +@@ -27,7 +25,2 @@ After that, go to the `packages/backend` directory and run + ```bash +-AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x \ +-AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x \ +-AUTH_OAUTH2_CLIENT_ID=x AUTH_OAUTH2_CLIENT_SECRET=x \ +-AUTH_OAUTH2_AUTH_URL=x AUTH_OAUTH2_TOKEN_URL=x \ +-LOG_LEVEL=debug \ + yarn start +@@ -35,4 +28,4 @@ yarn start + +-Substitute `x` for actual values, or leave them as dummy values just to try out +-the backend without using the auth or sentry features. ++If you want to override any configuration locally, for example adding any secrets, ++you can do so in `app-config.local.yaml`. + +@@ -45,3 +38,3 @@ locations to the backend. These are places where the backend can find some + entity descriptor data to consume and serve. For more information, see +-[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog). ++[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). + +@@ -65,2 +58,2 @@ and + - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +-- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) ++- [Backstage Documentation](https://backstage.io/docs) +diff --git a/packages/backend/package.json b/packages/backend/package.json +index 2e377ca..f224eaf 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -18,19 +18,21 @@ + "dependencies": { ++ "@backstage/backend-common": "^0.19.9-next.1", ++ "@backstage/backend-tasks": "^0.5.12-next.1", ++ "@backstage/catalog-client": "^1.4.5", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/config": "^1.1.1", ++ "@backstage/plugin-app-backend": "^0.3.55-next.1", ++ "@backstage/plugin-auth-backend": "^0.20.0-next.1", ++ "@backstage/plugin-auth-node": "^0.4.1-next.1", ++ "@backstage/plugin-catalog-backend": "^1.15.0-next.1", ++ "@backstage/plugin-permission-common": "^0.7.9", ++ "@backstage/plugin-permission-node": "^0.7.18-next.1", ++ "@backstage/plugin-proxy-backend": "^0.4.5-next.1", ++ "@backstage/plugin-scaffolder-backend": "^1.19.0-next.1", ++ "@backstage/plugin-search-backend": "^1.4.7-next.1", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.16-next.1", ++ "@backstage/plugin-search-backend-node": "^1.2.11-next.1", ++ "@backstage/plugin-techdocs-backend": "^1.9.0-next.1", + "app": "link:../app", +- "@backstage/backend-common": "^0.14.0", +- "@backstage/backend-tasks": "^0.3.2", +- "@backstage/catalog-model": "^1.0.3", +- "@backstage/catalog-client": "^1.0.3", +- "@backstage/config": "^1.0.1", +- "@backstage/plugin-app-backend": "^0.3.33", +- "@backstage/plugin-auth-backend": "^0.14.1", +- "@backstage/plugin-catalog-backend": "^1.2.0", +- "@backstage/plugin-permission-common": "^0.6.2", +- "@backstage/plugin-permission-node": "^0.6.2", +- "@backstage/plugin-proxy-backend": "^0.2.27", +- "@backstage/plugin-scaffolder-backend": "^1.3.0", +- "@backstage/plugin-search-backend": "^0.5.3", +- "@backstage/plugin-search-backend-module-pg": "^0.3.4", +- "@backstage/plugin-search-backend-node": "^0.6.2", +- "@backstage/plugin-techdocs-backend": "^1.1.2", ++ "better-sqlite3": "^8.0.0", + "dockerode": "^3.3.1", +@@ -42,8 +44,7 @@ + "devDependencies": { +- "@backstage/cli": "^0.17.2", ++ "@backstage/cli": "^0.24.0-next.1", + "@types/dockerode": "^3.3.0", +- "@types/express-serve-static-core": "^4.17.5", + "@types/express": "^4.17.6", +- "@types/luxon": "^2.0.4", +- "better-sqlite3": "^7.5.0" ++ "@types/express-serve-static-core": "^4.17.5", ++ "@types/luxon": "^2.0.4" + }, +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 70bc66b..04c4ff9 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -17,3 +17,3 @@ import { + DatabaseManager, +- SingleHostDiscovery, ++ HostDiscovery, + UrlReaders, +@@ -32,2 +32,3 @@ import { PluginEnvironment } from './types'; + import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; + +@@ -36,7 +37,11 @@ function makeCreateEnv(config: Config) { + const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++ const discovery = HostDiscovery.fromConfig(config); + const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++ const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); + const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); ++ const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); ++ ++ const identity = DefaultIdentityClient.create({ ++ discovery, ++ }); + const permissions = ServerPermissionClient.fromConfig(config, { +@@ -63,2 +68,3 @@ function makeCreateEnv(config: Config) { + permissions, ++ identity, + }; +@@ -106,3 +112,3 @@ module.hot?.accept(); + main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); ++ console.error('Backend failed to start up', error); + process.exit(1); +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +index 159116d..77eb6aa 100644 +--- a/packages/backend/src/plugins/auth.ts ++++ b/packages/backend/src/plugins/auth.ts +@@ -27,3 +27,3 @@ export default async function createPlugin( + // +- // If you want to use a production ready resolver you can switch to the ++ // If you want to use a production ready resolver you can switch to + // the one that is commented out below, it looks up a user entity in the +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +index 7ce5fcf..a12fee2 100644 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ b/packages/backend/src/plugins/scaffolder.ts +@@ -18,2 +18,4 @@ export default async function createPlugin( + catalogClient, ++ identity: env.identity, ++ permissions: env.permissions, + }); +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +index 8e0a864..9cd2c74 100644 +--- a/packages/backend/src/types.ts ++++ b/packages/backend/src/types.ts +@@ -11,2 +11,3 @@ import { PluginTaskScheduler } from '@backstage/backend-tasks'; + import { PermissionEvaluator } from '@backstage/plugin-permission-common'; ++import { IdentityApi } from '@backstage/plugin-auth-node'; + +@@ -22,2 +23,3 @@ export type PluginEnvironment = { + permissions: PermissionEvaluator; ++ identity: IdentityApi; + }; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..37c7fb1 +--- /dev/null ++++ b/playwright.config.ts +@@ -0,0 +1,60 @@ ++/* ++ * Copyright 2023 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { defineConfig } from '@playwright/test'; ++import { generateProjects } from '@backstage/e2e-test-utils/playwright'; ++ ++/** ++ * See https://playwright.dev/docs/test-configuration. ++ */ ++export default defineConfig({ ++ timeout: 60_000, ++ ++ expect: { ++ timeout: 5_000, ++ }, ++ ++ // Run your local dev server before starting the tests ++ webServer: process.env.CI ++ ? [] ++ : [ ++ { ++ command: 'yarn start', ++ port: 3000, ++ reuseExistingServer: true, ++ timeout: 60_000, ++ }, ++ ], ++ ++ forbidOnly: !!process.env.CI, ++ ++ retries: process.env.CI ? 2 : 0, ++ ++ reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]], ++ ++ use: { ++ actionTimeout: 0, ++ baseURL: ++ process.env.PLAYWRIGHT_URL ?? ++ (process.env.CI ? 'http://localhost:7007' : 'http://localhost:3000'), ++ screenshot: 'only-on-failure', ++ trace: 'on-first-retry', ++ }, ++ ++ outputDir: 'node_modules/.cache/e2e-test-results', ++ ++ projects: generateProjects(), // Find all packages with e2e-test folders ++}); +diff --git a/plugins/README.md b/plugins/README.md +index 58dc32c..d7865fd 100644 +--- a/plugins/README.md ++++ b/plugins/README.md +@@ -6,3 +6,3 @@ separate folder of its own. + If you want to create a new plugin here, go to your project root directory, run +-the command `yarn backstage-cli create`, and follow the on-screen instructions. ++the command `yarn new`, and follow the on-screen instructions. + +diff --git a/yarn.lock b/yarn.lock +new file mode 100644 +index 0000000..5ad7fe2 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1 @@ ++# intentionally left empty diff --git a/diffs/0.4.29..0.5.7-next.1.diff b/diffs/0.4.29..0.5.7-next.1.diff new file mode 100644 index 00000000..6fcfd7d5 --- /dev/null +++ b/diffs/0.4.29..0.5.7-next.1.diff @@ -0,0 +1,988 @@ +diff --git a/.dockerignore b/.dockerignore +index 505a7b5..05edb62 100644 +--- a/.dockerignore ++++ b/.dockerignore +@@ -1,2 +1,4 @@ + .git ++.yarn/cache ++.yarn/install-state.gz + node_modules +diff --git a/.eslintignore b/.eslintignore +new file mode 100644 +index 0000000..e5b1994 +--- /dev/null ++++ b/.eslintignore +@@ -0,0 +1 @@ ++playwright.config.ts +diff --git a/.gitignore b/.gitignore +index d16a8d3..fbf8139 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -17,2 +17,11 @@ node_modules/ + ++# Yarn 3 files ++.pnp.* ++.yarn/* ++!.yarn/patches ++!.yarn/plugins ++!.yarn/releases ++!.yarn/sdks ++!.yarn/versions ++ + # Node version directives +@@ -39 +48,7 @@ site + *-credentials.yaml ++ ++# vscode database functionality support files ++*.session.sql ++ ++# E2E test reports ++e2e-test-report/ +diff --git a/app-config.production.yaml b/app-config.production.yaml +index 6535d96..8f0751c 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -11,10 +11,5 @@ backend: + baseUrl: http://localhost:7007 +- listen: +- port: 7007 +- # The following host directive binds to all IPv4 interfaces when its value +- # is "0.0.0.0". This is the most permissive setting. The right value depends +- # on your specific deployment. If you remove the host line entirely, the +- # backend will bind on the interface that corresponds to the backend.baseUrl +- # hostname. +- host: 0.0.0.0 ++ # The listener can also be expressed as a single : string. In this case we bind to ++ # all interfaces, the most permissive setting. The right value depends on your specific deployment. ++ listen: ':7007' + +@@ -37,3 +32,3 @@ catalog: + # Overrides the default list locations from app-config.yaml as these contain example data. +- # See https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog for more details ++ # See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details + # on how to get entities into the catalog. +diff --git a/app-config.yaml b/app-config.yaml +index cd6998d..08d2abf 100644 +--- a/app-config.yaml ++++ b/app-config.yaml +@@ -9,3 +9,3 @@ backend: + # Used for enabling authentication, secret is shared by all backend plugins +- # See https://backstage.io/docs/tutorials/backend-to-backend-auth for ++ # See https://backstage.io/docs/auth/service-to-service-auth for + # information on the format +@@ -17,5 +17,4 @@ backend: + port: 7007 +- # Uncomment the following host directive to bind to all IPv4 interfaces and +- # not just the baseUrl hostname. +- # host: 0.0.0.0 ++ # Uncomment the following host directive to bind to specific interfaces ++ # host: 127.0.0.1 + csp: +@@ -26,5 +25,5 @@ backend: + origin: http://localhost:3000 +- methods: [GET, POST, PUT, DELETE] ++ methods: [GET, HEAD, PATCH, POST, PUT, DELETE] + credentials: true +- # This is for local developement only, it is not recommended to use this in production ++ # This is for local development only, it is not recommended to use this in production + # The production database configuration is stored in app-config.production.yaml +@@ -33,4 +32,2 @@ backend: + connection: ':memory:' +- cache: +- store: memory + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir +@@ -49,5 +46,8 @@ integrations: + proxy: +- '/test': +- target: 'https://example.com' +- changeOrigin: true ++ ### Example for how to add a proxy endpoint for the frontend. ++ ### A typical reason to do this is to handle HTTPS and CORS for internal services. ++ # endpoints: ++ # '/test': ++ # target: 'https://example.com' ++ # changeOrigin: true + +diff --git a/backstage.json b/backstage.json +index d19a958..707af29 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "1.4.0" ++ "version": "1.20.0-next.1" + } +diff --git a/examples/template/template.yaml b/examples/template/template.yaml +index 50052b7..33f262b 100644 +--- a/examples/template/template.yaml ++++ b/examples/template/template.yaml +@@ -63,3 +63,3 @@ spec: + input: +- repoContentsUrl: ${{ steps.publish.output.repoContentsUrl }} ++ repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} + catalogInfoPath: '/catalog-info.yaml' +@@ -70,5 +70,5 @@ spec: + - title: Repository +- url: ${{ steps.publish.output.remoteUrl }} ++ url: ${{ steps['publish'].output.remoteUrl }} + - title: Open in catalog + icon: catalog +- entityRef: ${{ steps.register.output.entityRef }} ++ entityRef: ${{ steps['register'].output.entityRef }} +diff --git a/lerna.json b/lerna.json +index 322929d..529a62f 100644 +--- a/lerna.json ++++ b/lerna.json +@@ -3,4 +3,4 @@ + "npmClient": "yarn", +- "useWorkspaces": true, +- "version": "0.1.0" ++ "version": "0.1.0", ++ "$schema": "node_modules/lerna/schemas/lerna-schema.json" + } +diff --git a/package.json b/package.json +index 03b8e70..2364da8 100644 +--- a/package.json ++++ b/package.json +@@ -5,3 +5,3 @@ + "engines": { +- "node": "14 || 16" ++ "node": "18 || 20" + }, +@@ -11,3 +11,4 @@ + "start-backend": "yarn workspace backend start", +- "build": "backstage-cli repo build --all", ++ "build:backend": "yarn workspace backend build", ++ "build:all": "backstage-cli repo build --all", + "build-image": "yarn workspace backend build-image", +@@ -15,6 +16,7 @@ + "tsc:full": "tsc --skipLibCheck false --incremental false", +- "clean": "backstage-cli clean && lerna run clean", +- "diff": "lerna run diff --", +- "test": "backstage-cli test", +- "test:all": "lerna run test -- --coverage", ++ "clean": "backstage-cli repo clean", ++ "test": "backstage-cli repo test", ++ "test:all": "backstage-cli repo test --coverage", ++ "test:e2e": "playwright test", ++ "fix": "backstage-cli repo fix", + "lint": "backstage-cli repo lint --since origin/master", +@@ -22,4 +24,3 @@ + "prettier:check": "prettier --check .", +- "create-plugin": "backstage-cli create-plugin --scope internal", +- "remove-plugin": "backstage-cli remove-plugin" ++ "new": "backstage-cli new --scope internal" + }, +@@ -32,9 +33,11 @@ + "devDependencies": { +- "@backstage/cli": "^0.18.0", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/e2e-test-utils": "^0.1.0", ++ "@playwright/test": "^1.32.3", + "@spotify/prettier-config": "^12.0.0", +- "concurrently": "^6.0.0", +- "lerna": "^4.0.0", ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^9.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.6.4", +- "node-gyp": "^9.0.0" ++ "typescript": "~5.2.0" + }, +diff --git a/packages/app/.eslintignore b/packages/app/.eslintignore +new file mode 100644 +index 0000000..a48cf0d +--- /dev/null ++++ b/packages/app/.eslintignore +@@ -0,0 +1 @@ ++public +diff --git a/packages/app/cypress.json b/packages/app/cypress.json +deleted file mode 100644 +index 0cb845a..0000000 +--- a/packages/app/cypress.json ++++ /dev/null +@@ -1,6 +0,0 @@ +-{ +- "baseUrl": "http://localhost:3001", +- "fixturesFolder": false, +- "pluginsFile": false, +- "retries": 3 +-} +diff --git a/packages/app/cypress/.eslintrc.json b/packages/app/cypress/.eslintrc.json +deleted file mode 100644 +index 2b3a458..0000000 +--- a/packages/app/cypress/.eslintrc.json ++++ /dev/null +@@ -1,21 +0,0 @@ +-{ +- "plugins": ["cypress"], +- "extends": ["plugin:cypress/recommended"], +- "rules": { +- "jest/expect-expect": [ +- "error", +- { +- "assertFunctionNames": ["expect", "cy.contains"] +- } +- ], +- "import/no-extraneous-dependencies": [ +- "error", +- { +- "devDependencies": true, +- "optionalDependencies": true, +- "peerDependencies": true, +- "bundledDependencies": true +- } +- ] +- } +-} +diff --git a/packages/app/cypress/integration/app.js b/packages/app/cypress/integration/app.js +deleted file mode 100644 +index 43fb2e3..0000000 +--- a/packages/app/cypress/integration/app.js ++++ /dev/null +@@ -1,6 +0,0 @@ +-describe('App', () => { +- it('should render the catalog', () => { +- cy.visit('/'); +- cy.contains('My Company Catalog'); +- }); +-}); +diff --git a/packages/app/e2e-tests/app.test.ts b/packages/app/e2e-tests/app.test.ts +new file mode 100644 +index 0000000..d45bc0d +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,23 @@ ++/* ++ * Copyright 2020 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { test, expect } from '@playwright/test'; ++ ++test('App should render the welcome page', async ({ page }) => { ++ await page.goto('/'); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index d88156a..2100405 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -8,28 +8,35 @@ + }, ++ "scripts": { ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "clean": "backstage-cli package clean", ++ "test": "backstage-cli package test", ++ "lint": "backstage-cli package lint" ++ }, + "dependencies": { +- "@backstage/app-defaults": "^1.0.4", +- "@backstage/catalog-model": "^1.1.0", +- "@backstage/cli": "^0.18.0", +- "@backstage/core-app-api": "^1.0.4", +- "@backstage/core-components": "^0.10.0", +- "@backstage/core-plugin-api": "^1.0.4", +- "@backstage/integration-react": "^1.1.2", +- "@backstage/plugin-api-docs": "^0.8.7", +- "@backstage/plugin-catalog": "^1.4.0", +- "@backstage/plugin-catalog-common": "^1.0.4", +- "@backstage/plugin-catalog-graph": "^0.2.19", +- "@backstage/plugin-catalog-import": "^0.8.10", +- "@backstage/plugin-catalog-react": "^1.1.2", +- "@backstage/plugin-github-actions": "^0.5.7", +- "@backstage/plugin-org": "^0.5.7", +- "@backstage/plugin-permission-react": "^0.4.3", +- "@backstage/plugin-scaffolder": "^1.4.0", +- "@backstage/plugin-search": "^1.0.0", +- "@backstage/plugin-search-react": "^1.0.0", +- "@backstage/plugin-tech-radar": "^0.5.14", +- "@backstage/plugin-techdocs": "^1.3.0", +- "@backstage/plugin-techdocs-react": "^1.0.2", +- "@backstage/plugin-techdocs-module-addons-contrib": "^1.0.2", +- "@backstage/plugin-user-settings": "^0.4.6", +- "@backstage/theme": "^0.2.16", ++ "@backstage/app-defaults": "^1.4.5-next.1", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/core-app-api": "^1.11.1-next.0", ++ "@backstage/core-components": "^0.13.8-next.1", ++ "@backstage/core-plugin-api": "^1.8.0-next.0", ++ "@backstage/integration-react": "^1.1.21-next.1", ++ "@backstage/plugin-api-docs": "^0.9.13-next.1", ++ "@backstage/plugin-catalog": "^1.15.0-next.1", ++ "@backstage/plugin-catalog-common": "^1.0.17", ++ "@backstage/plugin-catalog-graph": "^0.2.38-next.1", ++ "@backstage/plugin-catalog-import": "^0.10.2-next.1", ++ "@backstage/plugin-catalog-react": "^1.9.0-next.1", ++ "@backstage/plugin-github-actions": "^0.6.7-next.1", ++ "@backstage/plugin-org": "^0.6.16-next.1", ++ "@backstage/plugin-permission-react": "^0.4.17-next.0", ++ "@backstage/plugin-scaffolder": "^1.16.0-next.1", ++ "@backstage/plugin-search": "^1.4.2-next.1", ++ "@backstage/plugin-search-react": "^1.7.2-next.1", ++ "@backstage/plugin-tech-radar": "^0.6.10-next.1", ++ "@backstage/plugin-techdocs": "^1.8.1-next.1", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.2-next.1", ++ "@backstage/plugin-techdocs-react": "^1.1.13-next.1", ++ "@backstage/plugin-user-settings": "^0.7.12-next.1", ++ "@backstage/theme": "^0.4.4-next.0", + "@material-ui/core": "^4.12.2", +@@ -39,29 +46,15 @@ + "react-dom": "^17.0.2", +- "react-router": "6.0.0-beta.0", +- "react-router-dom": "6.0.0-beta.0", +- "react-use": "^15.3.3" ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^1.1.2", ++ "@backstage/test-utils": "^1.4.5-next.0", ++ "@playwright/test": "^1.32.3", + "@testing-library/jest-dom": "^5.10.1", + "@testing-library/react": "^12.1.3", +- "@testing-library/user-event": "^12.0.7", +- "@types/jest": "^26.0.7", +- "@types/node": "^14.14.32", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^8.0.0", + "@types/react-dom": "*", +- "cross-env": "^7.0.0", +- "cypress": "^9.7.0", +- "eslint-plugin-cypress": "^2.10.3", +- "start-server-and-test": "^1.10.11" +- }, +- "scripts": { +- "start": "backstage-cli package start", +- "build": "backstage-cli package build", +- "clean": "backstage-cli package clean", +- "test": "backstage-cli package test", +- "lint": "backstage-cli package lint", +- "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", +- "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", +- "cy:dev": "cypress open", +- "cy:run": "cypress run --browser chrome" ++ "cross-env": "^7.0.0" + }, +diff --git a/packages/app/public/index.html b/packages/app/public/index.html +index a936c73..fc758ee 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -10,3 +10,2 @@ + /> +- + +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index c487726..8d62f29 100644 +--- a/packages/app/src/App.tsx ++++ b/packages/app/src/App.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { Navigate, Route } from 'react-router'; ++import { Navigate, Route } from 'react-router-dom'; + import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; +@@ -31,5 +31,5 @@ import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; + import { createApp } from '@backstage/app-defaults'; +-import { FlatRoutes } from '@backstage/core-app-api'; ++import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; + import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; +-import { PermissionedRoute } from '@backstage/plugin-permission-react'; ++import { RequirePermission } from '@backstage/plugin-permission-react'; + import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; +@@ -42,2 +42,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -48,2 +49,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -55,8 +57,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -83,6 +82,9 @@ const routes = ( + /> +- } ++ element={ ++ ++ ++ ++ } + /> +@@ -96,4 +98,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -103,5 +105,3 @@ const App = () => ( + +- ++ , + ); +- +-export default App; +diff --git a/packages/app/src/components/Root/LogoFull.tsx b/packages/app/src/components/Root/LogoFull.tsx +index c7b1c84..47e3b73 100644 +--- a/packages/app/src/components/Root/LogoFull.tsx ++++ b/packages/app/src/components/Root/LogoFull.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/LogoIcon.tsx b/packages/app/src/components/Root/LogoIcon.tsx +index 073cf6e..7eae8c7 100644 +--- a/packages/app/src/components/Root/LogoIcon.tsx ++++ b/packages/app/src/components/Root/LogoIcon.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx +index b1164a3..6768b48 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,19 +1,3 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React, { PropsWithChildren } from 'react'; +-import { Link, makeStyles } from '@material-ui/core'; ++import { makeStyles } from '@material-ui/core'; + import HomeIcon from '@material-ui/icons/Home'; +@@ -25,3 +9,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -41,2 +24,3 @@ import { + useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -66,9 +50,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +diff --git a/packages/app/src/components/Root/index.ts b/packages/app/src/components/Root/index.ts +index dff706f..3528881 100644 +--- a/packages/app/src/components/Root/index.ts ++++ b/packages/app/src/components/Root/index.ts +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + export { Root } from './Root'; +diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx +index 6ec4da0..6722ea2 100644 +--- a/packages/app/src/components/catalog/EntityPage.tsx ++++ b/packages/app/src/components/catalog/EntityPage.tsx +@@ -1,16 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ + import React from 'react'; +@@ -42,2 +27,4 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +@@ -118,2 +105,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -326,3 +321,6 @@ const systemPage = ( + +- ++ ++ ++ ++ + +diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx +index 928b820..1788dde 100644 +--- a/packages/app/src/components/search/SearchPage.tsx ++++ b/packages/app/src/components/search/SearchPage.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; ++import { makeStyles, Theme, Grid, Paper } from '@material-ui/core'; + +@@ -12,3 +12,2 @@ import { SearchType } from '@backstage/plugin-search'; + import { +- DefaultResultListItem, + SearchBar, +@@ -16,2 +15,3 @@ import { + SearchResult, ++ SearchPagination, + useSearch, +@@ -111,38 +111,6 @@ const SearchPage = () => { + ++ + +- {({ results }) => ( +- +- {results.map(({ type, document, highlight, rank }) => { +- switch (type) { +- case 'software-catalog': +- return ( +- +- ); +- case 'techdocs': +- return ( +- +- ); +- default: +- return ( +- +- ); +- } +- })} +- +- )} ++ } /> ++ } /> + +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index a5773aa..18548e9 100644 +--- a/packages/backend/Dockerfile ++++ b/packages/backend/Dockerfile +@@ -7,3 +7,3 @@ + # yarn tsc +-# yarn build ++# yarn build:backend + # +@@ -11,11 +11,28 @@ + +-FROM node:16-bullseye-slim ++FROM node:18-bookworm-slim + ++# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends python3 g++ build-essential && \ ++ yarn config set python /usr/bin/python3 ++ ++# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, ++# in which case you should also move better-sqlite3 to "devDependencies" in package.json. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends libsqlite3-dev ++ ++# From here on we use the least-privileged `node` user to run the backend. ++USER node ++ ++# This should create the app dir as `node`. ++# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. ++# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. + WORKDIR /app + +-# install sqlite3 dependencies, you can skip this if you don't use sqlite3 in the image +-RUN apt-get update && \ +- apt-get install -y --no-install-recommends libsqlite3-dev python3 build-essential && \ +- rm -rf /var/lib/apt/lists/* && \ +- yarn config set python /usr/bin/python3 ++# This switches many Node.js dependencies to production mode. ++ENV NODE_ENV production + +@@ -24,9 +41,10 @@ RUN apt-get update && \ + # and along with yarn.lock and the root package.json, that's enough to run yarn install. +-COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ ++COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ + RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz + +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + + # Then copy the rest of the backend bundle, along with any other files we might want. +-COPY packages/backend/dist/bundle.tar.gz app-config*.yaml ./ ++COPY --chown=node:node packages/backend/dist/bundle.tar.gz app-config*.yaml ./ + RUN tar xzf bundle.tar.gz && rm bundle.tar.gz +diff --git a/packages/backend/README.md b/packages/backend/README.md +index 02426ef..3607b0a 100644 +--- a/packages/backend/README.md ++++ b/packages/backend/README.md +@@ -18,4 +18,2 @@ To run the example backend, first go to the project root and run + yarn install +-yarn tsc +-yarn build + ``` +@@ -27,7 +25,2 @@ After that, go to the `packages/backend` directory and run + ```bash +-AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x \ +-AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x \ +-AUTH_OAUTH2_CLIENT_ID=x AUTH_OAUTH2_CLIENT_SECRET=x \ +-AUTH_OAUTH2_AUTH_URL=x AUTH_OAUTH2_TOKEN_URL=x \ +-LOG_LEVEL=debug \ + yarn start +@@ -35,4 +28,4 @@ yarn start + +-Substitute `x` for actual values, or leave them as dummy values just to try out +-the backend without using the auth or sentry features. ++If you want to override any configuration locally, for example adding any secrets, ++you can do so in `app-config.local.yaml`. + +@@ -45,3 +38,3 @@ locations to the backend. These are places where the backend can find some + entity descriptor data to consume and serve. For more information, see +-[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog). ++[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). + +@@ -65,2 +58,2 @@ and + - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +-- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) ++- [Backstage Documentation](https://backstage.io/docs) +diff --git a/packages/backend/package.json b/packages/backend/package.json +index fa1c517..f224eaf 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -18,19 +18,21 @@ + "dependencies": { ++ "@backstage/backend-common": "^0.19.9-next.1", ++ "@backstage/backend-tasks": "^0.5.12-next.1", ++ "@backstage/catalog-client": "^1.4.5", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/config": "^1.1.1", ++ "@backstage/plugin-app-backend": "^0.3.55-next.1", ++ "@backstage/plugin-auth-backend": "^0.20.0-next.1", ++ "@backstage/plugin-auth-node": "^0.4.1-next.1", ++ "@backstage/plugin-catalog-backend": "^1.15.0-next.1", ++ "@backstage/plugin-permission-common": "^0.7.9", ++ "@backstage/plugin-permission-node": "^0.7.18-next.1", ++ "@backstage/plugin-proxy-backend": "^0.4.5-next.1", ++ "@backstage/plugin-scaffolder-backend": "^1.19.0-next.1", ++ "@backstage/plugin-search-backend": "^1.4.7-next.1", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.16-next.1", ++ "@backstage/plugin-search-backend-node": "^1.2.11-next.1", ++ "@backstage/plugin-techdocs-backend": "^1.9.0-next.1", + "app": "link:../app", +- "@backstage/backend-common": "^0.14.1", +- "@backstage/backend-tasks": "^0.3.3", +- "@backstage/catalog-model": "^1.1.0", +- "@backstage/catalog-client": "^1.0.4", +- "@backstage/config": "^1.0.1", +- "@backstage/plugin-app-backend": "^0.3.34", +- "@backstage/plugin-auth-backend": "^0.15.0", +- "@backstage/plugin-catalog-backend": "^1.3.0", +- "@backstage/plugin-permission-common": "^0.6.3", +- "@backstage/plugin-permission-node": "^0.6.3", +- "@backstage/plugin-proxy-backend": "^0.2.28", +- "@backstage/plugin-scaffolder-backend": "^1.4.0", +- "@backstage/plugin-search-backend": "^1.0.0", +- "@backstage/plugin-search-backend-module-pg": "^0.3.5", +- "@backstage/plugin-search-backend-node": "^1.0.0", +- "@backstage/plugin-techdocs-backend": "^1.2.0", ++ "better-sqlite3": "^8.0.0", + "dockerode": "^3.3.1", +@@ -42,8 +44,7 @@ + "devDependencies": { +- "@backstage/cli": "^0.18.0", ++ "@backstage/cli": "^0.24.0-next.1", + "@types/dockerode": "^3.3.0", +- "@types/express-serve-static-core": "^4.17.5", + "@types/express": "^4.17.6", +- "@types/luxon": "^2.0.4", +- "better-sqlite3": "^7.5.0" ++ "@types/express-serve-static-core": "^4.17.5", ++ "@types/luxon": "^2.0.4" + }, +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 70bc66b..04c4ff9 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -17,3 +17,3 @@ import { + DatabaseManager, +- SingleHostDiscovery, ++ HostDiscovery, + UrlReaders, +@@ -32,2 +32,3 @@ import { PluginEnvironment } from './types'; + import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; + +@@ -36,7 +37,11 @@ function makeCreateEnv(config: Config) { + const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++ const discovery = HostDiscovery.fromConfig(config); + const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++ const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); + const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); ++ const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); ++ ++ const identity = DefaultIdentityClient.create({ ++ discovery, ++ }); + const permissions = ServerPermissionClient.fromConfig(config, { +@@ -63,2 +68,3 @@ function makeCreateEnv(config: Config) { + permissions, ++ identity, + }; +@@ -106,3 +112,3 @@ module.hot?.accept(); + main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); ++ console.error('Backend failed to start up', error); + process.exit(1); +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +index 159116d..77eb6aa 100644 +--- a/packages/backend/src/plugins/auth.ts ++++ b/packages/backend/src/plugins/auth.ts +@@ -27,3 +27,3 @@ export default async function createPlugin( + // +- // If you want to use a production ready resolver you can switch to the ++ // If you want to use a production ready resolver you can switch to + // the one that is commented out below, it looks up a user entity in the +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +index 7ce5fcf..a12fee2 100644 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ b/packages/backend/src/plugins/scaffolder.ts +@@ -18,2 +18,4 @@ export default async function createPlugin( + catalogClient, ++ identity: env.identity, ++ permissions: env.permissions, + }); +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +index 8e0a864..9cd2c74 100644 +--- a/packages/backend/src/types.ts ++++ b/packages/backend/src/types.ts +@@ -11,2 +11,3 @@ import { PluginTaskScheduler } from '@backstage/backend-tasks'; + import { PermissionEvaluator } from '@backstage/plugin-permission-common'; ++import { IdentityApi } from '@backstage/plugin-auth-node'; + +@@ -22,2 +23,3 @@ export type PluginEnvironment = { + permissions: PermissionEvaluator; ++ identity: IdentityApi; + }; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..37c7fb1 +--- /dev/null ++++ b/playwright.config.ts +@@ -0,0 +1,60 @@ ++/* ++ * Copyright 2023 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { defineConfig } from '@playwright/test'; ++import { generateProjects } from '@backstage/e2e-test-utils/playwright'; ++ ++/** ++ * See https://playwright.dev/docs/test-configuration. ++ */ ++export default defineConfig({ ++ timeout: 60_000, ++ ++ expect: { ++ timeout: 5_000, ++ }, ++ ++ // Run your local dev server before starting the tests ++ webServer: process.env.CI ++ ? [] ++ : [ ++ { ++ command: 'yarn start', ++ port: 3000, ++ reuseExistingServer: true, ++ timeout: 60_000, ++ }, ++ ], ++ ++ forbidOnly: !!process.env.CI, ++ ++ retries: process.env.CI ? 2 : 0, ++ ++ reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]], ++ ++ use: { ++ actionTimeout: 0, ++ baseURL: ++ process.env.PLAYWRIGHT_URL ?? ++ (process.env.CI ? 'http://localhost:7007' : 'http://localhost:3000'), ++ screenshot: 'only-on-failure', ++ trace: 'on-first-retry', ++ }, ++ ++ outputDir: 'node_modules/.cache/e2e-test-results', ++ ++ projects: generateProjects(), // Find all packages with e2e-test folders ++}); +diff --git a/plugins/README.md b/plugins/README.md +index 58dc32c..d7865fd 100644 +--- a/plugins/README.md ++++ b/plugins/README.md +@@ -6,3 +6,3 @@ separate folder of its own. + If you want to create a new plugin here, go to your project root directory, run +-the command `yarn backstage-cli create`, and follow the on-screen instructions. ++the command `yarn new`, and follow the on-screen instructions. + +diff --git a/yarn.lock b/yarn.lock +new file mode 100644 +index 0000000..5ad7fe2 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1 @@ ++# intentionally left empty diff --git a/diffs/0.4.3..0.5.7-next.1.diff b/diffs/0.4.3..0.5.7-next.1.diff new file mode 100644 index 00000000..dc517900 --- /dev/null +++ b/diffs/0.4.3..0.5.7-next.1.diff @@ -0,0 +1,1918 @@ +diff --git a/.dockerignore b/.dockerignore +index 63c9c34..05edb62 100644 +--- a/.dockerignore ++++ b/.dockerignore +@@ -1,5 +1,8 @@ + .git ++.yarn/cache ++.yarn/install-state.gz + node_modules +-packages +-!packages/backend/dist ++packages/*/src ++packages/*/node_modules + plugins ++*.local.yaml +diff --git a/.eslintignore b/.eslintignore +new file mode 100644 +index 0000000..e5b1994 +--- /dev/null ++++ b/.eslintignore +@@ -0,0 +1 @@ ++playwright.config.ts +diff --git a/.gitignore b/.gitignore +index d16a8d3..fbf8139 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -17,2 +17,11 @@ node_modules/ + ++# Yarn 3 files ++.pnp.* ++.yarn/* ++!.yarn/patches ++!.yarn/plugins ++!.yarn/releases ++!.yarn/sdks ++!.yarn/versions ++ + # Node version directives +@@ -39 +48,7 @@ site + *-credentials.yaml ++ ++# vscode database functionality support files ++*.session.sql ++ ++# E2E test reports ++e2e-test-report/ +diff --git a/app-config.production.yaml b/app-config.production.yaml +index 92f4574..8f0751c 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -1,8 +1,35 @@ + app: +- # Should be the same as backend.baseUrl when using the `app-backend` plugin +- baseUrl: http://localhost:7000 ++ # Should be the same as backend.baseUrl when using the `app-backend` plugin. ++ baseUrl: http://localhost:7007 + + backend: +- baseUrl: http://localhost:7000 +- listen: +- port: 7000 ++ # Note that the baseUrl should be the URL that the browser and other clients ++ # should use when communicating with the backend, i.e. it needs to be ++ # reachable not just from within the backend host, but from all of your ++ # callers. When its value is "http://localhost:7007", it's strictly private ++ # and can't be reached by others. ++ baseUrl: http://localhost:7007 ++ # The listener can also be expressed as a single : string. In this case we bind to ++ # all interfaces, the most permissive setting. The right value depends on your specific deployment. ++ listen: ':7007' ++ ++ # config options: https://node-postgres.com/api/client ++ database: ++ client: pg ++ connection: ++ host: ${POSTGRES_HOST} ++ port: ${POSTGRES_PORT} ++ user: ${POSTGRES_USER} ++ password: ${POSTGRES_PASSWORD} ++ # https://node-postgres.com/features/ssl ++ # you can set the sslmode configuration option via the `PGSSLMODE` environment variable ++ # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require) ++ # ssl: ++ # ca: # if you have a CA file and want to verify it you can uncomment this section ++ # $file: /ca/server.crt ++ ++catalog: ++ # Overrides the default list locations from app-config.yaml as these contain example data. ++ # See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details ++ # on how to get entities into the catalog. ++ locations: [] +diff --git a/app-config.yaml b/app-config.yaml +index 93b0c3f..08d2abf 100644 +--- a/app-config.yaml ++++ b/app-config.yaml +@@ -8,5 +8,13 @@ organization: + backend: +- baseUrl: http://localhost:7000 ++ # Used for enabling authentication, secret is shared by all backend plugins ++ # See https://backstage.io/docs/auth/service-to-service-auth for ++ # information on the format ++ # auth: ++ # keys: ++ # - secret: ${BACKEND_SECRET} ++ baseUrl: http://localhost:7007 + listen: +- port: 7000 ++ port: 7007 ++ # Uncomment the following host directive to bind to specific interfaces ++ # host: 127.0.0.1 + csp: +@@ -17,9 +25,9 @@ backend: + origin: http://localhost:3000 +- methods: [GET, POST, PUT, DELETE] ++ methods: [GET, HEAD, PATCH, POST, PUT, DELETE] + credentials: true ++ # This is for local development only, it is not recommended to use this in production ++ # The production database configuration is stored in app-config.production.yaml + database: +- client: sqlite3 ++ client: better-sqlite3 + connection: ':memory:' +- cache: +- store: memory + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir +@@ -29,2 +37,4 @@ integrations: + - host: github.com ++ # This is a Personal Access Token or PAT from GitHub. You can find out how to generate this token, and more information ++ # about setting up the GitHub integration here: https://backstage.io/docs/getting-started/configuration#setting-up-a-github-integration + token: ${GITHUB_TOKEN} +@@ -36,5 +46,8 @@ integrations: + proxy: +- '/test': +- target: 'https://example.com' +- changeOrigin: true ++ ### Example for how to add a proxy endpoint for the frontend. ++ ### A typical reason to do this is to handle HTTPS and CORS for internal services. ++ # endpoints: ++ # '/test': ++ # target: 'https://example.com' ++ # changeOrigin: true + +@@ -56,46 +69,35 @@ auth: + scaffolder: +- github: +- token: ${GITHUB_TOKEN} +- visibility: public # or 'internal' or 'private' ++ # see https://backstage.io/docs/features/software-templates/configuration for software template options + + catalog: ++ import: ++ entityFilename: catalog-info.yaml ++ pullRequestBranchName: backstage-integration + rules: +- - allow: [Component, System, API, Group, User, Resource, Location] ++ - allow: [Component, System, API, Resource, Location] + locations: +- # Backstage example components +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-components.yaml +- +- # Backstage example systems +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-systems.yaml +- +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml +- +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml ++ # Local example data, file locations are relative to the backend process, typically `packages/backend` ++ - type: file ++ target: ../../examples/entities.yaml + +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.yaml +- +- # Backstage example templates +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/react-ssr-template/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/springboot-grpc-template/template.yaml ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml + rules: + - allow: [Template] +- - type: url +- target: https://github.com/spotify/cookiecutter-golang/blob/master/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/docs-template/template.yaml ++ ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml + rules: +- - allow: [Template] ++ - allow: [User, Group] ++ ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml ++ ++ ## Uncomment these lines to add an example org ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme-corp.yaml ++ # rules: ++ # - allow: [User, Group] +diff --git a/backstage.json b/backstage.json +new file mode 100644 +index 0000000..707af29 +--- /dev/null ++++ b/backstage.json +@@ -0,0 +1,3 @@ ++{ ++ "version": "1.20.0-next.1" ++} +diff --git a/examples/entities.yaml b/examples/entities.yaml +new file mode 100644 +index 0000000..447e8b1 +--- /dev/null ++++ b/examples/entities.yaml +@@ -0,0 +1,41 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system ++apiVersion: backstage.io/v1alpha1 ++kind: System ++metadata: ++ name: examples ++spec: ++ owner: guests ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: example-website ++spec: ++ type: website ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ providesApis: [example-grpc-api] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api ++apiVersion: backstage.io/v1alpha1 ++kind: API ++metadata: ++ name: example-grpc-api ++spec: ++ type: grpc ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ definition: | ++ syntax = "proto3"; ++ ++ service Exampler { ++ rpc Example (ExampleMessage) returns (ExampleMessage) {}; ++ } ++ ++ message ExampleMessage { ++ string example = 1; ++ }; +diff --git a/examples/org.yaml b/examples/org.yaml +new file mode 100644 +index 0000000..a10e81f +--- /dev/null ++++ b/examples/org.yaml +@@ -0,0 +1,17 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user ++apiVersion: backstage.io/v1alpha1 ++kind: User ++metadata: ++ name: guest ++spec: ++ memberOf: [guests] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group ++apiVersion: backstage.io/v1alpha1 ++kind: Group ++metadata: ++ name: guests ++spec: ++ type: team ++ children: [] +diff --git a/examples/template/content/catalog-info.yaml b/examples/template/content/catalog-info.yaml +new file mode 100644 +index 0000000..d4ccca4 +--- /dev/null ++++ b/examples/template/content/catalog-info.yaml +@@ -0,0 +1,8 @@ ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: ${{ values.name | dump }} ++spec: ++ type: service ++ owner: user:guest ++ lifecycle: experimental +diff --git a/examples/template/content/index.js b/examples/template/content/index.js +new file mode 100644 +index 0000000..071ce5a +--- /dev/null ++++ b/examples/template/content/index.js +@@ -0,0 +1 @@ ++console.log('Hello from ${{ values.name }}!'); +diff --git a/examples/template/content/package.json b/examples/template/content/package.json +new file mode 100644 +index 0000000..86f968a +--- /dev/null ++++ b/examples/template/content/package.json +@@ -0,0 +1,5 @@ ++{ ++ "name": "${{ values.name }}", ++ "private": true, ++ "dependencies": {} ++} +diff --git a/examples/template/template.yaml b/examples/template/template.yaml +new file mode 100644 +index 0000000..33f262b +--- /dev/null ++++ b/examples/template/template.yaml +@@ -0,0 +1,74 @@ ++apiVersion: scaffolder.backstage.io/v1beta3 ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template ++kind: Template ++metadata: ++ name: example-nodejs-template ++ title: Example Node.js Template ++ description: An example template for the scaffolder that creates a simple Node.js service ++spec: ++ owner: user:guest ++ type: service ++ ++ # These parameters are used to generate the input form in the frontend, and are ++ # used to gather input data for the execution of the template. ++ parameters: ++ - title: Fill in some steps ++ required: ++ - name ++ properties: ++ name: ++ title: Name ++ type: string ++ description: Unique name of the component ++ ui:autofocus: true ++ ui:options: ++ rows: 5 ++ - title: Choose a location ++ required: ++ - repoUrl ++ properties: ++ repoUrl: ++ title: Repository Location ++ type: string ++ ui:field: RepoUrlPicker ++ ui:options: ++ allowedHosts: ++ - github.com ++ ++ # These steps are executed in the scaffolder backend, using data that we gathered ++ # via the parameters above. ++ steps: ++ # Each step executes an action, in this case one templates files into the working directory. ++ - id: fetch-base ++ name: Fetch Base ++ action: fetch:template ++ input: ++ url: ./content ++ values: ++ name: ${{ parameters.name }} ++ ++ # This step publishes the contents of the working directory to GitHub. ++ - id: publish ++ name: Publish ++ action: publish:github ++ input: ++ allowedHosts: ['github.com'] ++ description: This is ${{ parameters.name }} ++ repoUrl: ${{ parameters.repoUrl }} ++ ++ # The final step is to register our new component in the catalog. ++ - id: register ++ name: Register ++ action: catalog:register ++ input: ++ repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} ++ catalogInfoPath: '/catalog-info.yaml' ++ ++ # Outputs are displayed to the user after a successful execution of the template. ++ output: ++ links: ++ - title: Repository ++ url: ${{ steps['publish'].output.remoteUrl }} ++ - title: Open in catalog ++ icon: catalog ++ entityRef: ${{ steps['register'].output.entityRef }} +diff --git a/lerna.json b/lerna.json +index 322929d..529a62f 100644 +--- a/lerna.json ++++ b/lerna.json +@@ -3,4 +3,4 @@ + "npmClient": "yarn", +- "useWorkspaces": true, +- "version": "0.1.0" ++ "version": "0.1.0", ++ "$schema": "node_modules/lerna/schemas/lerna-schema.json" + } +diff --git a/package.json b/package.json +index c63b504..2364da8 100644 +--- a/package.json ++++ b/package.json +@@ -5,3 +5,3 @@ + "engines": { +- "node": "14 || 16" ++ "node": "18 || 20" + }, +@@ -11,3 +11,4 @@ + "start-backend": "yarn workspace backend start", +- "build": "lerna run build", ++ "build:backend": "yarn workspace backend build", ++ "build:all": "backstage-cli repo build --all", + "build-image": "yarn workspace backend build-image", +@@ -15,11 +16,11 @@ + "tsc:full": "tsc --skipLibCheck false --incremental false", +- "clean": "backstage-cli clean && lerna run clean", +- "diff": "lerna run diff --", +- "test": "backstage-cli test", +- "test:all": "lerna run test -- --coverage", +- "lint": "lerna run lint --since origin/master --", +- "lint:all": "lerna run lint --", ++ "clean": "backstage-cli repo clean", ++ "test": "backstage-cli repo test", ++ "test:all": "backstage-cli repo test --coverage", ++ "test:e2e": "playwright test", ++ "fix": "backstage-cli repo fix", ++ "lint": "backstage-cli repo lint --since origin/master", ++ "lint:all": "backstage-cli repo lint", + "prettier:check": "prettier --check .", +- "create-plugin": "backstage-cli create-plugin --scope internal", +- "remove-plugin": "backstage-cli remove-plugin" ++ "new": "backstage-cli new --scope internal" + }, +@@ -32,7 +33,15 @@ + "devDependencies": { +- "@backstage/cli": "^0.8.2", +- "@spotify/prettier-config": "^11.0.0", +- "concurrently": "^6.0.0", +- "lerna": "^4.0.0", +- "prettier": "^2.3.2" ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/e2e-test-utils": "^0.1.0", ++ "@playwright/test": "^1.32.3", ++ "@spotify/prettier-config": "^12.0.0", ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^9.0.0", ++ "prettier": "^2.3.2", ++ "typescript": "~5.2.0" ++ }, ++ "resolutions": { ++ "@types/react": "^17", ++ "@types/react-dom": "^17" + }, +@@ -40,3 +49,3 @@ + "lint-staged": { +- "*.{js,jsx,ts,tsx}": [ ++ "*.{js,jsx,ts,tsx,mjs,cjs}": [ + "eslint --fix", +diff --git a/packages/README.md b/packages/README.md +new file mode 100644 +index 0000000..6327fa0 +--- /dev/null ++++ b/packages/README.md +@@ -0,0 +1,9 @@ ++# The Packages Folder ++ ++This is where your own applications and centrally managed libraries live, each ++in a separate folder of its own. ++ ++From the start there's an `app` folder (for the frontend) and a `backend` folder ++(for the Node backend), but you can also add more modules in here that house ++your core additions and adaptations, such as themes, common React component ++libraries, utilities, and similar. +diff --git a/packages/app/.eslintignore b/packages/app/.eslintignore +new file mode 100644 +index 0000000..a48cf0d +--- /dev/null ++++ b/packages/app/.eslintignore +@@ -0,0 +1 @@ ++public +diff --git a/packages/app/.eslintrc.js b/packages/app/.eslintrc.js +index 13573ef..e2a53a6 100644 +--- a/packages/app/.eslintrc.js ++++ b/packages/app/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/app/cypress.json b/packages/app/cypress.json +deleted file mode 100644 +index 5de7ebf..0000000 +--- a/packages/app/cypress.json ++++ /dev/null +@@ -1,5 +0,0 @@ +-{ +- "baseUrl": "http://localhost:3001", +- "fixturesFolder": false, +- "pluginsFile": false +-} +diff --git a/packages/app/cypress/.eslintrc.json b/packages/app/cypress/.eslintrc.json +deleted file mode 100644 +index 2b3a458..0000000 +--- a/packages/app/cypress/.eslintrc.json ++++ /dev/null +@@ -1,21 +0,0 @@ +-{ +- "plugins": ["cypress"], +- "extends": ["plugin:cypress/recommended"], +- "rules": { +- "jest/expect-expect": [ +- "error", +- { +- "assertFunctionNames": ["expect", "cy.contains"] +- } +- ], +- "import/no-extraneous-dependencies": [ +- "error", +- { +- "devDependencies": true, +- "optionalDependencies": true, +- "peerDependencies": true, +- "bundledDependencies": true +- } +- ] +- } +-} +diff --git a/packages/app/cypress/integration/app.js b/packages/app/cypress/integration/app.js +deleted file mode 100644 +index 43fb2e3..0000000 +--- a/packages/app/cypress/integration/app.js ++++ /dev/null +@@ -1,6 +0,0 @@ +-describe('App', () => { +- it('should render the catalog', () => { +- cy.visit('/'); +- cy.contains('My Company Catalog'); +- }); +-}); +diff --git a/packages/app/e2e-tests/app.test.ts b/packages/app/e2e-tests/app.test.ts +new file mode 100644 +index 0000000..d45bc0d +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,23 @@ ++/* ++ * Copyright 2020 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { test, expect } from '@playwright/test'; ++ ++test('App should render the welcome page', async ({ page }) => { ++ await page.goto('/'); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index b585e49..2100405 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,22 +5,38 @@ + "bundled": true, ++ "backstage": { ++ "role": "frontend" ++ }, ++ "scripts": { ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "clean": "backstage-cli package clean", ++ "test": "backstage-cli package test", ++ "lint": "backstage-cli package lint" ++ }, + "dependencies": { +- "@backstage/catalog-model": "^0.9.6", +- "@backstage/cli": "^0.8.2", +- "@backstage/core-app-api": "^0.1.20", +- "@backstage/core-components": "^0.7.3", +- "@backstage/core-plugin-api": "^0.1.13", +- "@backstage/integration-react": "^0.1.13", +- "@backstage/plugin-api-docs": "^0.6.13", +- "@backstage/plugin-catalog": "^0.7.2", +- "@backstage/plugin-catalog-import": "^0.7.3", +- "@backstage/plugin-catalog-react": "^0.6.3", +- "@backstage/plugin-github-actions": "^0.4.23", +- "@backstage/plugin-org": "^0.3.27", +- "@backstage/plugin-scaffolder": "^0.11.10", +- "@backstage/plugin-search": "^0.4.17", +- "@backstage/plugin-tech-radar": "^0.4.11", +- "@backstage/plugin-techdocs": "^0.12.5", +- "@backstage/plugin-user-settings": "^0.3.10", +- "@backstage/test-utils": "^0.1.21", +- "@backstage/theme": "^0.2.13", ++ "@backstage/app-defaults": "^1.4.5-next.1", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/core-app-api": "^1.11.1-next.0", ++ "@backstage/core-components": "^0.13.8-next.1", ++ "@backstage/core-plugin-api": "^1.8.0-next.0", ++ "@backstage/integration-react": "^1.1.21-next.1", ++ "@backstage/plugin-api-docs": "^0.9.13-next.1", ++ "@backstage/plugin-catalog": "^1.15.0-next.1", ++ "@backstage/plugin-catalog-common": "^1.0.17", ++ "@backstage/plugin-catalog-graph": "^0.2.38-next.1", ++ "@backstage/plugin-catalog-import": "^0.10.2-next.1", ++ "@backstage/plugin-catalog-react": "^1.9.0-next.1", ++ "@backstage/plugin-github-actions": "^0.6.7-next.1", ++ "@backstage/plugin-org": "^0.6.16-next.1", ++ "@backstage/plugin-permission-react": "^0.4.17-next.0", ++ "@backstage/plugin-scaffolder": "^1.16.0-next.1", ++ "@backstage/plugin-search": "^1.4.2-next.1", ++ "@backstage/plugin-search-react": "^1.7.2-next.1", ++ "@backstage/plugin-tech-radar": "^0.6.10-next.1", ++ "@backstage/plugin-techdocs": "^1.8.1-next.1", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.2-next.1", ++ "@backstage/plugin-techdocs-react": "^1.1.13-next.1", ++ "@backstage/plugin-user-settings": "^0.7.12-next.1", ++ "@backstage/theme": "^0.4.4-next.0", + "@material-ui/core": "^4.12.2", +@@ -28,30 +44,17 @@ + "history": "^5.0.0", +- "react": "^16.13.1", +- "react-dom": "^16.13.1", +- "react-router": "6.0.0-beta.0", +- "react-router-dom": "6.0.0-beta.0", +- "react-use": "^15.3.3" ++ "react": "^17.0.2", ++ "react-dom": "^17.0.2", ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { ++ "@backstage/test-utils": "^1.4.5-next.0", ++ "@playwright/test": "^1.32.3", + "@testing-library/jest-dom": "^5.10.1", +- "@testing-library/react": "^10.4.1", +- "@testing-library/user-event": "^12.0.7", +- "@types/jest": "^26.0.7", +- "@types/node": "^14.14.32", ++ "@testing-library/react": "^12.1.3", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^8.0.0", + "@types/react-dom": "*", +- "cross-env": "^7.0.0", +- "cypress": "^7.3.0", +- "eslint-plugin-cypress": "^2.10.3", +- "start-server-and-test": "^1.10.11" +- }, +- "scripts": { +- "start": "backstage-cli app:serve", +- "build": "backstage-cli app:build", +- "test": "backstage-cli test", +- "lint": "backstage-cli lint", +- "clean": "backstage-cli clean", +- "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", +- "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", +- "cy:dev": "cypress open", +- "cy:run": "cypress run" ++ "cross-env": "^7.0.0" + }, +diff --git a/packages/app/public/index.html b/packages/app/public/index.html +index b93896c..fc758ee 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -10,3 +10,2 @@ + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index 82bc479..b94cac7 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -12,5 +12,5 @@ describe('App', () => { + app: { title: 'Test' }, +- backend: { baseUrl: 'http://localhost:7000' }, ++ backend: { baseUrl: 'http://localhost:7007' }, + techdocs: { +- storageUrl: 'http://localhost:7000/api/techdocs/static/docs', ++ storageUrl: 'http://localhost:7007/api/techdocs/static/docs', + }, +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 4cd8368..8d62f29 100644 +--- a/packages/app/src/App.tsx ++++ b/packages/app/src/App.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { Navigate, Route } from 'react-router'; ++import { Navigate, Route } from 'react-router-dom'; + import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; +@@ -13,2 +13,3 @@ import { + import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; ++import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +@@ -16,3 +17,2 @@ import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -21,2 +21,4 @@ import { + } from '@backstage/plugin-techdocs'; ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; + import { UserSettingsPage } from '@backstage/plugin-user-settings'; +@@ -28,3 +30,7 @@ import { Root } from './components/Root'; + import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; +-import { createApp, FlatRoutes } from '@backstage/core-app-api'; ++import { createApp } from '@backstage/app-defaults'; ++import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; ++import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; ++import { RequirePermission } from '@backstage/plugin-permission-react'; ++import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; + +@@ -36,5 +42,6 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); + bind(apiDocsPlugin.externalRoutes, { +- createComponent: scaffolderPlugin.routes.root, ++ registerApi: catalogImportPlugin.routes.importPage, + }); +@@ -42,2 +49,6 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, ++ }); ++ bind(orgPlugin.externalRoutes, { ++ catalogIndex: catalogPlugin.routes.catalogIndex, + }); +@@ -46,8 +57,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -59,5 +67,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -72,3 +82,10 @@ const routes = ( + /> +- } /> ++ ++ ++ ++ } ++ /> + }> +@@ -77,2 +94,3 @@ const routes = ( + } /> ++ } /> + +@@ -80,4 +98,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -87,5 +105,3 @@ const App = () => ( + +- ++ , + ); +- +-export default App; +diff --git a/packages/app/src/components/Root/LogoFull.tsx b/packages/app/src/components/Root/LogoFull.tsx +index c7b1c84..47e3b73 100644 +--- a/packages/app/src/components/Root/LogoFull.tsx ++++ b/packages/app/src/components/Root/LogoFull.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/LogoIcon.tsx b/packages/app/src/components/Root/LogoIcon.tsx +index 073cf6e..7eae8c7 100644 +--- a/packages/app/src/components/Root/LogoIcon.tsx ++++ b/packages/app/src/components/Root/LogoIcon.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx +index ec59b0b..6768b48 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,19 +1,3 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- +-import React, { useContext, PropsWithChildren } from 'react'; +-import { Link, makeStyles } from '@material-ui/core'; ++import React, { PropsWithChildren } from 'react'; ++import { makeStyles } from '@material-ui/core'; + import HomeIcon from '@material-ui/icons/Home'; +@@ -25,4 +9,6 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; +-import { Settings as SidebarSettings } from '@backstage/plugin-user-settings'; ++import { ++ Settings as SidebarSettings, ++ UserSettingsSignInAvatar, ++} from '@backstage/plugin-user-settings'; + import { SidebarSearchModal } from '@backstage/plugin-search'; +@@ -30,10 +16,14 @@ import { + Sidebar, +- SidebarPage, + sidebarConfig, +- SidebarContext, +- SidebarItem, + SidebarDivider, +- SidebarSpace, ++ SidebarGroup, ++ SidebarItem, ++ SidebarPage, + SidebarScrollWrapper, ++ SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; ++import MenuIcon from '@material-ui/icons/Menu'; ++import SearchIcon from '@material-ui/icons/Search'; + +@@ -56,3 +46,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -60,8 +50,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -76,17 +61,27 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ } to="/search"> ++ ++ + +- {/* Global nav, not org-specific */} +- +- +- +- +- {/* End global nav */} +- +- +- +- ++ }> ++ {/* Global nav, not org-specific */} ++ ++ ++ ++ ++ {/* End global nav */} ++ ++ ++ ++ ++ + + +- ++ } ++ to="/settings" ++ > ++ ++ + +diff --git a/packages/app/src/components/Root/index.ts b/packages/app/src/components/Root/index.ts +index dff706f..3528881 100644 +--- a/packages/app/src/components/Root/index.ts ++++ b/packages/app/src/components/Root/index.ts +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + export { Root } from './Root'; +diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx +index d3b4b78..6722ea2 100644 +--- a/packages/app/src/components/catalog/EntityPage.tsx ++++ b/packages/app/src/components/catalog/EntityPage.tsx +@@ -1,16 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ + import React from 'react'; +@@ -29,3 +14,2 @@ import { + EntityDependsOnResourcesCard, +- EntitySystemDiagramCard, + EntityHasComponentsCard, +@@ -43,2 +27,4 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +@@ -56,2 +42,27 @@ import { EntityTechdocsContent } from '@backstage/plugin-techdocs'; + import { EmptyState } from '@backstage/core-components'; ++import { ++ Direction, ++ EntityCatalogGraphCard, ++} from '@backstage/plugin-catalog-graph'; ++import { ++ RELATION_API_CONSUMED_BY, ++ RELATION_API_PROVIDED_BY, ++ RELATION_CONSUMES_API, ++ RELATION_DEPENDENCY_OF, ++ RELATION_DEPENDS_ON, ++ RELATION_HAS_PART, ++ RELATION_PART_OF, ++ RELATION_PROVIDES_API, ++} from '@backstage/catalog-model'; ++ ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); + +@@ -94,2 +105,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -110,2 +129,6 @@ const overviewContent = ( + ++ ++ ++ ++ + +@@ -152,3 +175,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -179,3 +202,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -198,3 +221,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -225,2 +248,5 @@ const apiPage = ( + ++ ++ ++ + +@@ -292,3 +318,9 @@ const systemPage = ( + +- ++ ++ ++ ++ ++ ++ ++ + +@@ -304,3 +336,19 @@ const systemPage = ( + +- ++ + +@@ -317,2 +365,5 @@ const domainPage = ( + ++ ++ ++ + +diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx +index 7b3c2b2..1788dde 100644 +--- a/packages/app/src/components/search/SearchPage.tsx ++++ b/packages/app/src/components/search/SearchPage.tsx +@@ -1,5 +1,12 @@ + import React from 'react'; +-import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; ++import { makeStyles, Theme, Grid, Paper } from '@material-ui/core'; + +-import { CatalogResultListItem } from '@backstage/plugin-catalog'; ++import { CatalogSearchResultListItem } from '@backstage/plugin-catalog'; ++import { ++ catalogApiRef, ++ CATALOG_FILTER_EXISTS, ++} from '@backstage/plugin-catalog-react'; ++import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; ++ ++import { SearchType } from '@backstage/plugin-search'; + import { +@@ -8,5 +15,13 @@ import { + SearchResult, +- DefaultResultListItem, +-} from '@backstage/plugin-search'; +-import { Content, Header, Page } from '@backstage/core-components'; ++ SearchPagination, ++ useSearch, ++} from '@backstage/plugin-search-react'; ++import { ++ CatalogIcon, ++ Content, ++ DocsIcon, ++ Header, ++ Page, ++} from '@backstage/core-components'; ++import { useApi } from '@backstage/core-plugin-api'; + +@@ -18,2 +33,3 @@ const useStyles = makeStyles((theme: Theme) => ({ + padding: theme.spacing(2), ++ marginTop: theme.spacing(2), + }, +@@ -28,2 +44,4 @@ const SearchPage = () => { + const classes = useStyles(); ++ const { types } = useSearch(); ++ const catalogApi = useApi(catalogApiRef); + +@@ -36,3 +54,3 @@ const SearchPage = () => { + +- ++ + +@@ -40,5 +58,43 @@ const SearchPage = () => { + ++ , ++ }, ++ { ++ value: 'techdocs', ++ name: 'Documentation', ++ icon: , ++ }, ++ ]} ++ /> + ++ {types.includes('techdocs') && ( ++ { ++ // Return a list of entities which are documented. ++ const { items } = await catalogApi.getEntities({ ++ fields: ['metadata.name'], ++ filter: { ++ 'metadata.annotations.backstage.io/techdocs-ref': ++ CATALOG_FILTER_EXISTS, ++ }, ++ }); ++ ++ const names = items.map(entity => entity.metadata.name); ++ names.sort(); ++ return names; ++ }} ++ /> ++ )} + { + className={classes.filter} ++ label="Lifecycle" + name="lifecycle" +@@ -54,25 +111,6 @@ const SearchPage = () => { + ++ + +- {({ results }) => ( +- +- {results.map(({ type, document }) => { +- switch (type) { +- case 'software-catalog': +- return ( +- +- ); +- default: +- return ( +- +- ); +- } +- })} +- +- )} ++ } /> ++ } /> + +diff --git a/packages/backend/.eslintrc.js b/packages/backend/.eslintrc.js +index 16a033d..e2a53a6 100644 +--- a/packages/backend/.eslintrc.js ++++ b/packages/backend/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint.backend')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index 31231a3..18548e9 100644 +--- a/packages/backend/Dockerfile ++++ b/packages/backend/Dockerfile +@@ -7,3 +7,3 @@ + # yarn tsc +-# yarn build ++# yarn build:backend + # +@@ -11,6 +11,29 @@ + +-FROM node:14-buster-slim ++FROM node:18-bookworm-slim + ++# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends python3 g++ build-essential && \ ++ yarn config set python /usr/bin/python3 ++ ++# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, ++# in which case you should also move better-sqlite3 to "devDependencies" in package.json. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends libsqlite3-dev ++ ++# From here on we use the least-privileged `node` user to run the backend. ++USER node ++ ++# This should create the app dir as `node`. ++# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. ++# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. + WORKDIR /app + ++# This switches many Node.js dependencies to production mode. ++ENV NODE_ENV production ++ + # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. +@@ -18,11 +41,12 @@ WORKDIR /app + # and along with yarn.lock and the root package.json, that's enough to run yarn install. +-COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ ++COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ + RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz + +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + + # Then copy the rest of the backend bundle, along with any other files we might want. +-COPY packages/backend/dist/bundle.tar.gz app-config.yaml ./ ++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"] +diff --git a/packages/backend/README.md b/packages/backend/README.md +index 81e0f80..3607b0a 100644 +--- a/packages/backend/README.md ++++ b/packages/backend/README.md +@@ -18,4 +18,2 @@ To run the example backend, first go to the project root and run + yarn install +-yarn tsc +-yarn build + ``` +@@ -27,7 +25,2 @@ After that, go to the `packages/backend` directory and run + ```bash +-AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x \ +-AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x \ +-AUTH_OAUTH2_CLIENT_ID=x AUTH_OAUTH2_CLIENT_SECRET=x \ +-AUTH_OAUTH2_AUTH_URL=x AUTH_OAUTH2_TOKEN_URL=x \ +-LOG_LEVEL=debug \ + yarn start +@@ -35,6 +28,6 @@ yarn start + +-Substitute `x` for actual values, or leave them as dummy values just to try out +-the backend without using the auth or sentry features. ++If you want to override any configuration locally, for example adding any secrets, ++you can do so in `app-config.local.yaml`. + +-The backend starts up on port 7000 per default. ++The backend starts up on port 7007 per default. + +@@ -45,3 +38,3 @@ locations to the backend. These are places where the backend can find some + entity descriptor data to consume and serve. For more information, see +-[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog). ++[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). + +@@ -65,2 +58,2 @@ and + - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +-- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) ++- [Backstage Documentation](https://backstage.io/docs) +diff --git a/packages/backend/package.json b/packages/backend/package.json +index 0816912..f224eaf 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,32 +6,37 @@ + "private": true, ++ "backstage": { ++ "role": "backend" ++ }, + "scripts": { +- "build": "backstage-cli backend:bundle", +- "build-image": "docker build ../.. -f Dockerfile --tag backstage", +- "start": "backstage-cli backend:dev", +- "lint": "backstage-cli lint", +- "test": "backstage-cli test", +- "clean": "backstage-cli clean", +- "migrate:create": "knex migrate:make -x ts" ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "lint": "backstage-cli package lint", ++ "test": "backstage-cli package test", ++ "clean": "backstage-cli package clean", ++ "build-image": "docker build ../.. -f Dockerfile --tag backstage" + }, + "dependencies": { +- "app": "0.0.0", +- "@backstage/backend-common": "^0.9.9", +- "@backstage/catalog-model": "^0.9.6", +- "@backstage/catalog-client": "^0.5.1", +- "@backstage/config": "^0.1.11", +- "@backstage/plugin-app-backend": "^0.3.18", +- "@backstage/plugin-auth-backend": "^0.4.7", +- "@backstage/plugin-catalog-backend": "^0.17.3", +- "@backstage/plugin-proxy-backend": "^0.2.13", +- "@backstage/plugin-scaffolder-backend": "^0.15.12", +- "@backstage/plugin-search-backend": "^0.2.6", +- "@backstage/plugin-search-backend-node": "^0.4.3", +- "@backstage/plugin-techdocs-backend": "^0.10.7", +- "@gitbeaker/node": "^30.2.0", +- "@octokit/rest": "^18.5.3", +- "dockerode": "^3.2.1", ++ "@backstage/backend-common": "^0.19.9-next.1", ++ "@backstage/backend-tasks": "^0.5.12-next.1", ++ "@backstage/catalog-client": "^1.4.5", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/config": "^1.1.1", ++ "@backstage/plugin-app-backend": "^0.3.55-next.1", ++ "@backstage/plugin-auth-backend": "^0.20.0-next.1", ++ "@backstage/plugin-auth-node": "^0.4.1-next.1", ++ "@backstage/plugin-catalog-backend": "^1.15.0-next.1", ++ "@backstage/plugin-permission-common": "^0.7.9", ++ "@backstage/plugin-permission-node": "^0.7.18-next.1", ++ "@backstage/plugin-proxy-backend": "^0.4.5-next.1", ++ "@backstage/plugin-scaffolder-backend": "^1.19.0-next.1", ++ "@backstage/plugin-search-backend": "^1.4.7-next.1", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.16-next.1", ++ "@backstage/plugin-search-backend-node": "^1.2.11-next.1", ++ "@backstage/plugin-techdocs-backend": "^1.9.0-next.1", ++ "app": "link:../app", ++ "better-sqlite3": "^8.0.0", ++ "dockerode": "^3.3.1", + "express": "^4.17.1", + "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "pg": "^8.3.0", + "winston": "^3.2.1" +@@ -39,6 +44,7 @@ + "devDependencies": { +- "@backstage/cli": "^0.8.2", +- "@types/dockerode": "^3.2.1", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@types/dockerode": "^3.3.0", + "@types/express": "^4.17.6", +- "@types/express-serve-static-core": "^4.17.5" ++ "@types/express-serve-static-core": "^4.17.5", ++ "@types/luxon": "^2.0.4" + }, +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index f2b14b2..04c4ff9 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -17,5 +17,7 @@ import { + DatabaseManager, +- SingleHostDiscovery, ++ HostDiscovery, + UrlReaders, ++ ServerTokenManager, + } from '@backstage/backend-common'; ++import { TaskScheduler } from '@backstage/backend-tasks'; + import { Config } from '@backstage/config'; +@@ -29,2 +31,4 @@ import search from './plugins/search'; + import { PluginEnvironment } from './types'; ++import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; + +@@ -33,8 +37,17 @@ function makeCreateEnv(config: Config) { + const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++ const discovery = HostDiscovery.fromConfig(config); ++ const cacheManager = CacheManager.fromConfig(config); ++ const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); ++ const tokenManager = ServerTokenManager.noop(); ++ const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); + +- root.info(`Created UrlReader ${reader}`); ++ const identity = DefaultIdentityClient.create({ ++ discovery, ++ }); ++ const permissions = ServerPermissionClient.fromConfig(config, { ++ discovery, ++ tokenManager, ++ }); + +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++ root.info(`Created UrlReader ${reader}`); + +@@ -44,3 +57,15 @@ function makeCreateEnv(config: Config) { + const cache = cacheManager.forPlugin(plugin); +- return { logger, database, cache, config, reader, discovery }; ++ const scheduler = taskScheduler.forPlugin(plugin); ++ return { ++ logger, ++ database, ++ cache, ++ config, ++ reader, ++ discovery, ++ tokenManager, ++ scheduler, ++ permissions, ++ identity, ++ }; + }; +@@ -70,2 +95,4 @@ async function main() { + apiRouter.use('/search', await search(searchEnv)); ++ ++ // Add backends ABOVE this line; this 404 handler is the catch-all fallback + apiRouter.use(notFoundHandler()); +@@ -85,3 +112,3 @@ module.hot?.accept(); + main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); ++ console.error('Backend failed to start up', error); + process.exit(1); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +index 07fb04f..7c37f68 100644 +--- a/packages/backend/src/plugins/app.ts ++++ b/packages/backend/src/plugins/app.ts +@@ -4,9 +4,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + return await createRouter({ +- logger, +- config, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, + appPackageName: 'app', +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +index 5216510..77eb6aa 100644 +--- a/packages/backend/src/plugins/auth.ts ++++ b/packages/backend/src/plugins/auth.ts +@@ -1,2 +1,6 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; ++import { ++ createRouter, ++ providers, ++ defaultAuthProviderFactories, ++} from '@backstage/plugin-auth-backend'; + import { Router } from 'express'; +@@ -4,9 +8,47 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, database, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, ++ providerFactories: { ++ ...defaultAuthProviderFactories, ++ ++ // This replaces the default GitHub auth provider with a customized one. ++ // The `signIn` option enables sign-in for this provider, using the ++ // identity resolution logic that's provided in the `resolver` callback. ++ // ++ // This particular resolver makes all users share a single "guest" identity. ++ // It should only be used for testing and trying out Backstage. ++ // ++ // If you want to use a production ready resolver you can switch to ++ // the one that is commented out below, it looks up a user entity in the ++ // catalog using the GitHub username of the authenticated user. ++ // That resolver requires you to have user entities populated in the catalog, ++ // for example using https://backstage.io/docs/integrations/github/org ++ // ++ // There are other resolvers to choose from, and you can also create ++ // your own, see the auth documentation for more details: ++ // ++ // https://backstage.io/docs/auth/identity-resolver ++ github: providers.github.create({ ++ signIn: { ++ resolver(_, ctx) { ++ const userRef = 'user:default/guest'; // Must be a full entity reference ++ return ctx.issueToken({ ++ claims: { ++ sub: userRef, // The user's own identity ++ ent: [userRef], // A list of identities that the user claims ownership through ++ }, ++ }); ++ }, ++ // resolver: providers.github.resolvers.usernameMatchingUserEntityName(), ++ }, ++ }), ++ }, ++ }); + } +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +index 506f6d9..54ec393 100644 +--- a/packages/backend/src/plugins/proxy.ts ++++ b/packages/backend/src/plugins/proxy.ts +@@ -4,8 +4,10 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ }); + } +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +index 6be2e97..a12fee2 100644 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ b/packages/backend/src/plugins/scaffolder.ts +@@ -1,5 +1,3 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; + import { CatalogClient } from '@backstage/catalog-client'; + import { createRouter } from '@backstage/plugin-scaffolder-backend'; +-import Docker from 'dockerode'; + import { Router } from 'express'; +@@ -7,20 +5,17 @@ import type { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- database, +- reader, +- discovery, +-}: PluginEnvironment): Promise { +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- const catalogClient = new CatalogClient({ discoveryApi: discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ const catalogClient = new CatalogClient({ ++ discoveryApi: env.discovery, ++ }); + + return await createRouter({ +- containerRunner, +- logger, +- config, +- database, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ reader: env.reader, + catalogClient, +- reader, ++ identity: env.identity, ++ permissions: env.permissions, + }); +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +index 7fc317d..e9469dc 100644 +--- a/packages/backend/src/plugins/search.ts ++++ b/packages/backend/src/plugins/search.ts +@@ -7,18 +7,44 @@ import { + import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; ++import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend'; ++import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; ++import { Router } from 'express'; + +-export default async function createPlugin({ +- logger, +- discovery, +- config, +-}: PluginEnvironment) { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); ++ const searchEngine = new LunrSearchEngine({ ++ logger: env.logger, ++ }); ++ const indexBuilder = new IndexBuilder({ ++ logger: env.logger, ++ searchEngine, ++ }); ++ ++ const schedule = env.scheduler.createScheduledTaskRunner({ ++ frequency: { minutes: 10 }, ++ timeout: { minutes: 15 }, ++ // A 3 second delay gives the backend server a chance to initialize before ++ // any collators are executed, which may attempt requests against the API. ++ initialDelay: { seconds: 3 }, ++ }); + + // Collators are responsible for gathering documents known to plugins. This +- // particular collator gathers entities from the software catalog. ++ // collator gathers entities from the software catalog. ++ indexBuilder.addCollator({ ++ schedule, ++ factory: DefaultCatalogCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, ++ }), ++ }); ++ ++ // collator gathers entities from techdocs. + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { discovery }), ++ schedule, ++ factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ logger: env.logger, ++ tokenManager: env.tokenManager, ++ }), + }); +@@ -28,6 +54,4 @@ export default async function createPlugin({ + const { scheduler } = await indexBuilder.build(); ++ scheduler.start(); + +- // A 3 second delay gives the backend server a chance to initialize before +- // any collators are executed, which may attempt requests against the API. +- setTimeout(() => scheduler.start(), 3000); + useHotCleanup(module, () => scheduler.stop()); +@@ -36,3 +60,6 @@ export default async function createPlugin({ + engine: indexBuilder.getSearchEngine(), +- logger, ++ types: indexBuilder.getDocumentTypes(), ++ permissions: env.permissions, ++ config: env.config, ++ logger: env.logger, + }); +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +index 906d86d..be8bb0c 100644 +--- a/packages/backend/src/plugins/techdocs.ts ++++ b/packages/backend/src/plugins/techdocs.ts +@@ -11,12 +11,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, ++ const preparers = await Preparers.fromConfig(env.config, { ++ logger: env.logger, ++ reader: env.reader, + }); +@@ -28,4 +25,4 @@ export default async function createPlugin({ + // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, ++ const generators = await Generators.fromConfig(env.config, { ++ logger: env.logger, + containerRunner, +@@ -36,5 +33,5 @@ export default async function createPlugin({ + // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, ++ const publisher = await Publisher.fromConfig(env.config, { ++ logger: env.logger, ++ discovery: env.discovery, + }); +@@ -48,5 +45,6 @@ export default async function createPlugin({ + publisher, +- logger, +- config, +- discovery, ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ cache: env.cache, + }); +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +index 6c78a2a..9cd2c74 100644 +--- a/packages/backend/src/types.ts ++++ b/packages/backend/src/types.ts +@@ -6,4 +6,8 @@ import { + PluginEndpointDiscovery, ++ TokenManager, + UrlReader, + } from '@backstage/backend-common'; ++import { PluginTaskScheduler } from '@backstage/backend-tasks'; ++import { PermissionEvaluator } from '@backstage/plugin-permission-common'; ++import { IdentityApi } from '@backstage/plugin-auth-node'; + +@@ -16,2 +20,6 @@ export type PluginEnvironment = { + discovery: PluginEndpointDiscovery; ++ tokenManager: TokenManager; ++ scheduler: PluginTaskScheduler; ++ permissions: PermissionEvaluator; ++ identity: IdentityApi; + }; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..37c7fb1 +--- /dev/null ++++ b/playwright.config.ts +@@ -0,0 +1,60 @@ ++/* ++ * Copyright 2023 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { defineConfig } from '@playwright/test'; ++import { generateProjects } from '@backstage/e2e-test-utils/playwright'; ++ ++/** ++ * See https://playwright.dev/docs/test-configuration. ++ */ ++export default defineConfig({ ++ timeout: 60_000, ++ ++ expect: { ++ timeout: 5_000, ++ }, ++ ++ // Run your local dev server before starting the tests ++ webServer: process.env.CI ++ ? [] ++ : [ ++ { ++ command: 'yarn start', ++ port: 3000, ++ reuseExistingServer: true, ++ timeout: 60_000, ++ }, ++ ], ++ ++ forbidOnly: !!process.env.CI, ++ ++ retries: process.env.CI ? 2 : 0, ++ ++ reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]], ++ ++ use: { ++ actionTimeout: 0, ++ baseURL: ++ process.env.PLAYWRIGHT_URL ?? ++ (process.env.CI ? 'http://localhost:7007' : 'http://localhost:3000'), ++ screenshot: 'only-on-failure', ++ trace: 'on-first-retry', ++ }, ++ ++ outputDir: 'node_modules/.cache/e2e-test-results', ++ ++ projects: generateProjects(), // Find all packages with e2e-test folders ++}); +diff --git a/plugins/README.md b/plugins/README.md +new file mode 100644 +index 0000000..d7865fd +--- /dev/null ++++ b/plugins/README.md +@@ -0,0 +1,9 @@ ++# The Plugins Folder ++ ++This is where your own plugins and their associated modules live, each in a ++separate folder of its own. ++ ++If you want to create a new plugin here, go to your project root directory, run ++the command `yarn new`, and follow the on-screen instructions. ++ ++You can also check out existing plugins on [the plugin marketplace](https://backstage.io/plugins)! +diff --git a/yarn.lock b/yarn.lock +new file mode 100644 +index 0000000..5ad7fe2 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1 @@ ++# intentionally left empty diff --git a/diffs/0.4.30..0.5.7-next.1.diff b/diffs/0.4.30..0.5.7-next.1.diff new file mode 100644 index 00000000..d54ae368 --- /dev/null +++ b/diffs/0.4.30..0.5.7-next.1.diff @@ -0,0 +1,973 @@ +diff --git a/.dockerignore b/.dockerignore +index 505a7b5..05edb62 100644 +--- a/.dockerignore ++++ b/.dockerignore +@@ -1,2 +1,4 @@ + .git ++.yarn/cache ++.yarn/install-state.gz + node_modules +diff --git a/.eslintignore b/.eslintignore +new file mode 100644 +index 0000000..e5b1994 +--- /dev/null ++++ b/.eslintignore +@@ -0,0 +1 @@ ++playwright.config.ts +diff --git a/.gitignore b/.gitignore +index d16a8d3..fbf8139 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -17,2 +17,11 @@ node_modules/ + ++# Yarn 3 files ++.pnp.* ++.yarn/* ++!.yarn/patches ++!.yarn/plugins ++!.yarn/releases ++!.yarn/sdks ++!.yarn/versions ++ + # Node version directives +@@ -39 +48,7 @@ site + *-credentials.yaml ++ ++# vscode database functionality support files ++*.session.sql ++ ++# E2E test reports ++e2e-test-report/ +diff --git a/app-config.production.yaml b/app-config.production.yaml +index 6535d96..8f0751c 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -11,10 +11,5 @@ backend: + baseUrl: http://localhost:7007 +- listen: +- port: 7007 +- # The following host directive binds to all IPv4 interfaces when its value +- # is "0.0.0.0". This is the most permissive setting. The right value depends +- # on your specific deployment. If you remove the host line entirely, the +- # backend will bind on the interface that corresponds to the backend.baseUrl +- # hostname. +- host: 0.0.0.0 ++ # The listener can also be expressed as a single : string. In this case we bind to ++ # all interfaces, the most permissive setting. The right value depends on your specific deployment. ++ listen: ':7007' + +@@ -37,3 +32,3 @@ catalog: + # Overrides the default list locations from app-config.yaml as these contain example data. +- # See https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog for more details ++ # See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details + # on how to get entities into the catalog. +diff --git a/app-config.yaml b/app-config.yaml +index 4a058de..08d2abf 100644 +--- a/app-config.yaml ++++ b/app-config.yaml +@@ -9,3 +9,3 @@ backend: + # Used for enabling authentication, secret is shared by all backend plugins +- # See https://backstage.io/docs/tutorials/backend-to-backend-auth for ++ # See https://backstage.io/docs/auth/service-to-service-auth for + # information on the format +@@ -17,5 +17,4 @@ backend: + port: 7007 +- # Uncomment the following host directive to bind to all IPv4 interfaces and +- # not just the baseUrl hostname. +- # host: 0.0.0.0 ++ # Uncomment the following host directive to bind to specific interfaces ++ # host: 127.0.0.1 + csp: +@@ -33,4 +32,2 @@ backend: + connection: ':memory:' +- cache: +- store: memory + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir +@@ -49,5 +46,8 @@ integrations: + proxy: +- '/test': +- target: 'https://example.com' +- changeOrigin: true ++ ### Example for how to add a proxy endpoint for the frontend. ++ ### A typical reason to do this is to handle HTTPS and CORS for internal services. ++ # endpoints: ++ # '/test': ++ # target: 'https://example.com' ++ # changeOrigin: true + +diff --git a/backstage.json b/backstage.json +index 08df5a2..707af29 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "1.5.0" ++ "version": "1.20.0-next.1" + } +diff --git a/examples/template/template.yaml b/examples/template/template.yaml +index 50052b7..33f262b 100644 +--- a/examples/template/template.yaml ++++ b/examples/template/template.yaml +@@ -63,3 +63,3 @@ spec: + input: +- repoContentsUrl: ${{ steps.publish.output.repoContentsUrl }} ++ repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} + catalogInfoPath: '/catalog-info.yaml' +@@ -70,5 +70,5 @@ spec: + - title: Repository +- url: ${{ steps.publish.output.remoteUrl }} ++ url: ${{ steps['publish'].output.remoteUrl }} + - title: Open in catalog + icon: catalog +- entityRef: ${{ steps.register.output.entityRef }} ++ entityRef: ${{ steps['register'].output.entityRef }} +diff --git a/lerna.json b/lerna.json +index 322929d..529a62f 100644 +--- a/lerna.json ++++ b/lerna.json +@@ -3,4 +3,4 @@ + "npmClient": "yarn", +- "useWorkspaces": true, +- "version": "0.1.0" ++ "version": "0.1.0", ++ "$schema": "node_modules/lerna/schemas/lerna-schema.json" + } +diff --git a/package.json b/package.json +index d010cc8..2364da8 100644 +--- a/package.json ++++ b/package.json +@@ -5,3 +5,3 @@ + "engines": { +- "node": "14 || 16" ++ "node": "18 || 20" + }, +@@ -11,3 +11,4 @@ + "start-backend": "yarn workspace backend start", +- "build": "backstage-cli repo build --all", ++ "build:backend": "yarn workspace backend build", ++ "build:all": "backstage-cli repo build --all", + "build-image": "yarn workspace backend build-image", +@@ -15,6 +16,7 @@ + "tsc:full": "tsc --skipLibCheck false --incremental false", +- "clean": "backstage-cli clean && lerna run clean", +- "diff": "lerna run diff --", +- "test": "backstage-cli test", +- "test:all": "lerna run test -- --coverage", ++ "clean": "backstage-cli repo clean", ++ "test": "backstage-cli repo test", ++ "test:all": "backstage-cli repo test --coverage", ++ "test:e2e": "playwright test", ++ "fix": "backstage-cli repo fix", + "lint": "backstage-cli repo lint --since origin/master", +@@ -22,4 +24,3 @@ + "prettier:check": "prettier --check .", +- "create-plugin": "backstage-cli create-plugin --scope internal", +- "remove-plugin": "backstage-cli remove-plugin" ++ "new": "backstage-cli new --scope internal" + }, +@@ -32,9 +33,11 @@ + "devDependencies": { +- "@backstage/cli": "^0.18.1", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/e2e-test-utils": "^0.1.0", ++ "@playwright/test": "^1.32.3", + "@spotify/prettier-config": "^12.0.0", +- "concurrently": "^6.0.0", +- "lerna": "^4.0.0", ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^9.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.6.4", +- "node-gyp": "^9.0.0" ++ "typescript": "~5.2.0" + }, +diff --git a/packages/app/.eslintignore b/packages/app/.eslintignore +new file mode 100644 +index 0000000..a48cf0d +--- /dev/null ++++ b/packages/app/.eslintignore +@@ -0,0 +1 @@ ++public +diff --git a/packages/app/cypress.json b/packages/app/cypress.json +deleted file mode 100644 +index 0cb845a..0000000 +--- a/packages/app/cypress.json ++++ /dev/null +@@ -1,6 +0,0 @@ +-{ +- "baseUrl": "http://localhost:3001", +- "fixturesFolder": false, +- "pluginsFile": false, +- "retries": 3 +-} +diff --git a/packages/app/cypress/.eslintrc.json b/packages/app/cypress/.eslintrc.json +deleted file mode 100644 +index 2b3a458..0000000 +--- a/packages/app/cypress/.eslintrc.json ++++ /dev/null +@@ -1,21 +0,0 @@ +-{ +- "plugins": ["cypress"], +- "extends": ["plugin:cypress/recommended"], +- "rules": { +- "jest/expect-expect": [ +- "error", +- { +- "assertFunctionNames": ["expect", "cy.contains"] +- } +- ], +- "import/no-extraneous-dependencies": [ +- "error", +- { +- "devDependencies": true, +- "optionalDependencies": true, +- "peerDependencies": true, +- "bundledDependencies": true +- } +- ] +- } +-} +diff --git a/packages/app/cypress/integration/app.js b/packages/app/cypress/integration/app.js +deleted file mode 100644 +index 43fb2e3..0000000 +--- a/packages/app/cypress/integration/app.js ++++ /dev/null +@@ -1,6 +0,0 @@ +-describe('App', () => { +- it('should render the catalog', () => { +- cy.visit('/'); +- cy.contains('My Company Catalog'); +- }); +-}); +diff --git a/packages/app/e2e-tests/app.test.ts b/packages/app/e2e-tests/app.test.ts +new file mode 100644 +index 0000000..d45bc0d +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,23 @@ ++/* ++ * Copyright 2020 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { test, expect } from '@playwright/test'; ++ ++test('App should render the welcome page', async ({ page }) => { ++ await page.goto('/'); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 1d7aa24..2100405 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -8,28 +8,35 @@ + }, ++ "scripts": { ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "clean": "backstage-cli package clean", ++ "test": "backstage-cli package test", ++ "lint": "backstage-cli package lint" ++ }, + "dependencies": { +- "@backstage/app-defaults": "^1.0.5", +- "@backstage/catalog-model": "^1.1.0", +- "@backstage/cli": "^0.18.1", +- "@backstage/core-app-api": "^1.0.5", +- "@backstage/core-components": "^0.11.0", +- "@backstage/core-plugin-api": "^1.0.5", +- "@backstage/integration-react": "^1.1.3", +- "@backstage/plugin-api-docs": "^0.8.8", +- "@backstage/plugin-catalog": "^1.5.0", +- "@backstage/plugin-catalog-common": "^1.0.5", +- "@backstage/plugin-catalog-graph": "^0.2.20", +- "@backstage/plugin-catalog-import": "^0.8.11", +- "@backstage/plugin-catalog-react": "^1.1.3", +- "@backstage/plugin-github-actions": "^0.5.8", +- "@backstage/plugin-org": "^0.5.8", +- "@backstage/plugin-permission-react": "^0.4.4", +- "@backstage/plugin-scaffolder": "^1.5.0", +- "@backstage/plugin-search": "^1.0.1", +- "@backstage/plugin-search-react": "^1.0.1", +- "@backstage/plugin-tech-radar": "^0.5.15", +- "@backstage/plugin-techdocs": "^1.3.1", +- "@backstage/plugin-techdocs-react": "^1.0.3", +- "@backstage/plugin-techdocs-module-addons-contrib": "^1.0.3", +- "@backstage/plugin-user-settings": "^0.4.7", +- "@backstage/theme": "^0.2.16", ++ "@backstage/app-defaults": "^1.4.5-next.1", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/core-app-api": "^1.11.1-next.0", ++ "@backstage/core-components": "^0.13.8-next.1", ++ "@backstage/core-plugin-api": "^1.8.0-next.0", ++ "@backstage/integration-react": "^1.1.21-next.1", ++ "@backstage/plugin-api-docs": "^0.9.13-next.1", ++ "@backstage/plugin-catalog": "^1.15.0-next.1", ++ "@backstage/plugin-catalog-common": "^1.0.17", ++ "@backstage/plugin-catalog-graph": "^0.2.38-next.1", ++ "@backstage/plugin-catalog-import": "^0.10.2-next.1", ++ "@backstage/plugin-catalog-react": "^1.9.0-next.1", ++ "@backstage/plugin-github-actions": "^0.6.7-next.1", ++ "@backstage/plugin-org": "^0.6.16-next.1", ++ "@backstage/plugin-permission-react": "^0.4.17-next.0", ++ "@backstage/plugin-scaffolder": "^1.16.0-next.1", ++ "@backstage/plugin-search": "^1.4.2-next.1", ++ "@backstage/plugin-search-react": "^1.7.2-next.1", ++ "@backstage/plugin-tech-radar": "^0.6.10-next.1", ++ "@backstage/plugin-techdocs": "^1.8.1-next.1", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.2-next.1", ++ "@backstage/plugin-techdocs-react": "^1.1.13-next.1", ++ "@backstage/plugin-user-settings": "^0.7.12-next.1", ++ "@backstage/theme": "^0.4.4-next.0", + "@material-ui/core": "^4.12.2", +@@ -39,29 +46,15 @@ + "react-dom": "^17.0.2", +- "react-router": "6.0.0-beta.0", +- "react-router-dom": "6.0.0-beta.0", +- "react-use": "^15.3.3" ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^1.1.3", ++ "@backstage/test-utils": "^1.4.5-next.0", ++ "@playwright/test": "^1.32.3", + "@testing-library/jest-dom": "^5.10.1", + "@testing-library/react": "^12.1.3", +- "@testing-library/user-event": "^12.0.7", +- "@types/jest": "^26.0.7", +- "@types/node": "^14.14.32", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^8.0.0", + "@types/react-dom": "*", +- "cross-env": "^7.0.0", +- "cypress": "^9.7.0", +- "eslint-plugin-cypress": "^2.10.3", +- "start-server-and-test": "^1.10.11" +- }, +- "scripts": { +- "start": "backstage-cli package start", +- "build": "backstage-cli package build", +- "clean": "backstage-cli package clean", +- "test": "backstage-cli package test", +- "lint": "backstage-cli package lint", +- "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", +- "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", +- "cy:dev": "cypress open", +- "cy:run": "cypress run --browser chrome" ++ "cross-env": "^7.0.0" + }, +diff --git a/packages/app/public/index.html b/packages/app/public/index.html +index a936c73..fc758ee 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -10,3 +10,2 @@ + /> +- + +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index c487726..8d62f29 100644 +--- a/packages/app/src/App.tsx ++++ b/packages/app/src/App.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { Navigate, Route } from 'react-router'; ++import { Navigate, Route } from 'react-router-dom'; + import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; +@@ -31,5 +31,5 @@ import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; + import { createApp } from '@backstage/app-defaults'; +-import { FlatRoutes } from '@backstage/core-app-api'; ++import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; + import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; +-import { PermissionedRoute } from '@backstage/plugin-permission-react'; ++import { RequirePermission } from '@backstage/plugin-permission-react'; + import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; +@@ -42,2 +42,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -48,2 +49,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -55,8 +57,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -83,6 +82,9 @@ const routes = ( + /> +- } ++ element={ ++ ++ ++ ++ } + /> +@@ -96,4 +98,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -103,5 +105,3 @@ const App = () => ( + +- ++ , + ); +- +-export default App; +diff --git a/packages/app/src/components/Root/LogoFull.tsx b/packages/app/src/components/Root/LogoFull.tsx +index c7b1c84..47e3b73 100644 +--- a/packages/app/src/components/Root/LogoFull.tsx ++++ b/packages/app/src/components/Root/LogoFull.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/LogoIcon.tsx b/packages/app/src/components/Root/LogoIcon.tsx +index 073cf6e..7eae8c7 100644 +--- a/packages/app/src/components/Root/LogoIcon.tsx ++++ b/packages/app/src/components/Root/LogoIcon.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx +index b1164a3..6768b48 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,19 +1,3 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React, { PropsWithChildren } from 'react'; +-import { Link, makeStyles } from '@material-ui/core'; ++import { makeStyles } from '@material-ui/core'; + import HomeIcon from '@material-ui/icons/Home'; +@@ -25,3 +9,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -41,2 +24,3 @@ import { + useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -66,9 +50,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +diff --git a/packages/app/src/components/Root/index.ts b/packages/app/src/components/Root/index.ts +index dff706f..3528881 100644 +--- a/packages/app/src/components/Root/index.ts ++++ b/packages/app/src/components/Root/index.ts +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + export { Root } from './Root'; +diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx +index 6ec4da0..6722ea2 100644 +--- a/packages/app/src/components/catalog/EntityPage.tsx ++++ b/packages/app/src/components/catalog/EntityPage.tsx +@@ -1,16 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ + import React from 'react'; +@@ -42,2 +27,4 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +@@ -118,2 +105,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -326,3 +321,6 @@ const systemPage = ( + +- ++ ++ ++ ++ + +diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx +index 928b820..1788dde 100644 +--- a/packages/app/src/components/search/SearchPage.tsx ++++ b/packages/app/src/components/search/SearchPage.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; ++import { makeStyles, Theme, Grid, Paper } from '@material-ui/core'; + +@@ -12,3 +12,2 @@ import { SearchType } from '@backstage/plugin-search'; + import { +- DefaultResultListItem, + SearchBar, +@@ -16,2 +15,3 @@ import { + SearchResult, ++ SearchPagination, + useSearch, +@@ -111,38 +111,6 @@ const SearchPage = () => { + ++ + +- {({ results }) => ( +- +- {results.map(({ type, document, highlight, rank }) => { +- switch (type) { +- case 'software-catalog': +- return ( +- +- ); +- case 'techdocs': +- return ( +- +- ); +- default: +- return ( +- +- ); +- } +- })} +- +- )} ++ } /> ++ } /> + +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index 8836ac7..18548e9 100644 +--- a/packages/backend/Dockerfile ++++ b/packages/backend/Dockerfile +@@ -7,3 +7,3 @@ + # yarn tsc +-# yarn build ++# yarn build:backend + # +@@ -11,5 +11,10 @@ + +-FROM node:16-bullseye-slim ++FROM node:18-bookworm-slim + +-WORKDIR /app ++# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends python3 g++ build-essential && \ ++ yarn config set python /usr/bin/python3 + +@@ -17,6 +22,17 @@ WORKDIR /app + # in which case you should also move better-sqlite3 to "devDependencies" in package.json. +-RUN apt-get update && \ +- apt-get install -y --no-install-recommends libsqlite3-dev python3 build-essential && \ +- rm -rf /var/lib/apt/lists/* && \ +- yarn config set python /usr/bin/python3 ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends libsqlite3-dev ++ ++# From here on we use the least-privileged `node` user to run the backend. ++USER node ++ ++# This should create the app dir as `node`. ++# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. ++# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. ++WORKDIR /app ++ ++# This switches many Node.js dependencies to production mode. ++ENV NODE_ENV production + +@@ -25,9 +41,10 @@ RUN apt-get update && \ + # and along with yarn.lock and the root package.json, that's enough to run yarn install. +-COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ ++COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ + RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz + +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + + # Then copy the rest of the backend bundle, along with any other files we might want. +-COPY packages/backend/dist/bundle.tar.gz app-config*.yaml ./ ++COPY --chown=node:node packages/backend/dist/bundle.tar.gz app-config*.yaml ./ + RUN tar xzf bundle.tar.gz && rm bundle.tar.gz +diff --git a/packages/backend/README.md b/packages/backend/README.md +index 02426ef..3607b0a 100644 +--- a/packages/backend/README.md ++++ b/packages/backend/README.md +@@ -18,4 +18,2 @@ To run the example backend, first go to the project root and run + yarn install +-yarn tsc +-yarn build + ``` +@@ -27,7 +25,2 @@ After that, go to the `packages/backend` directory and run + ```bash +-AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x \ +-AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x \ +-AUTH_OAUTH2_CLIENT_ID=x AUTH_OAUTH2_CLIENT_SECRET=x \ +-AUTH_OAUTH2_AUTH_URL=x AUTH_OAUTH2_TOKEN_URL=x \ +-LOG_LEVEL=debug \ + yarn start +@@ -35,4 +28,4 @@ yarn start + +-Substitute `x` for actual values, or leave them as dummy values just to try out +-the backend without using the auth or sentry features. ++If you want to override any configuration locally, for example adding any secrets, ++you can do so in `app-config.local.yaml`. + +@@ -45,3 +38,3 @@ locations to the backend. These are places where the backend can find some + entity descriptor data to consume and serve. For more information, see +-[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog). ++[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). + +@@ -65,2 +58,2 @@ and + - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +-- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) ++- [Backstage Documentation](https://backstage.io/docs) +diff --git a/packages/backend/package.json b/packages/backend/package.json +index f005c39..f224eaf 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -18,20 +18,21 @@ + "dependencies": { ++ "@backstage/backend-common": "^0.19.9-next.1", ++ "@backstage/backend-tasks": "^0.5.12-next.1", ++ "@backstage/catalog-client": "^1.4.5", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/config": "^1.1.1", ++ "@backstage/plugin-app-backend": "^0.3.55-next.1", ++ "@backstage/plugin-auth-backend": "^0.20.0-next.1", ++ "@backstage/plugin-auth-node": "^0.4.1-next.1", ++ "@backstage/plugin-catalog-backend": "^1.15.0-next.1", ++ "@backstage/plugin-permission-common": "^0.7.9", ++ "@backstage/plugin-permission-node": "^0.7.18-next.1", ++ "@backstage/plugin-proxy-backend": "^0.4.5-next.1", ++ "@backstage/plugin-scaffolder-backend": "^1.19.0-next.1", ++ "@backstage/plugin-search-backend": "^1.4.7-next.1", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.16-next.1", ++ "@backstage/plugin-search-backend-node": "^1.2.11-next.1", ++ "@backstage/plugin-techdocs-backend": "^1.9.0-next.1", + "app": "link:../app", +- "@backstage/backend-common": "^0.15.0", +- "@backstage/backend-tasks": "^0.3.4", +- "@backstage/catalog-model": "^1.1.0", +- "@backstage/catalog-client": "^1.0.4", +- "@backstage/config": "^1.0.1", +- "@backstage/plugin-app-backend": "^0.3.35", +- "@backstage/plugin-auth-backend": "^0.15.1", +- "@backstage/plugin-catalog-backend": "^1.3.1", +- "@backstage/plugin-permission-common": "^0.6.3", +- "@backstage/plugin-permission-node": "^0.6.4", +- "@backstage/plugin-proxy-backend": "^0.2.29", +- "@backstage/plugin-scaffolder-backend": "^1.5.0", +- "@backstage/plugin-search-backend": "^1.0.1", +- "@backstage/plugin-search-backend-module-pg": "^0.3.6", +- "@backstage/plugin-search-backend-node": "^1.0.1", +- "@backstage/plugin-techdocs-backend": "^1.2.1", +- "better-sqlite3": "^7.5.0", ++ "better-sqlite3": "^8.0.0", + "dockerode": "^3.3.1", +@@ -43,6 +44,6 @@ + "devDependencies": { +- "@backstage/cli": "^0.18.1", ++ "@backstage/cli": "^0.24.0-next.1", + "@types/dockerode": "^3.3.0", +- "@types/express-serve-static-core": "^4.17.5", + "@types/express": "^4.17.6", ++ "@types/express-serve-static-core": "^4.17.5", + "@types/luxon": "^2.0.4" +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index ef05fa5..04c4ff9 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -17,3 +17,3 @@ import { + DatabaseManager, +- SingleHostDiscovery, ++ HostDiscovery, + UrlReaders, +@@ -32,2 +32,3 @@ import { PluginEnvironment } from './types'; + import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; + +@@ -36,7 +37,11 @@ function makeCreateEnv(config: Config) { + const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++ const discovery = HostDiscovery.fromConfig(config); + const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++ const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); + const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); ++ const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); ++ ++ const identity = DefaultIdentityClient.create({ ++ discovery, ++ }); + const permissions = ServerPermissionClient.fromConfig(config, { +@@ -63,2 +68,3 @@ function makeCreateEnv(config: Config) { + permissions, ++ identity, + }; +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +index 159116d..77eb6aa 100644 +--- a/packages/backend/src/plugins/auth.ts ++++ b/packages/backend/src/plugins/auth.ts +@@ -27,3 +27,3 @@ export default async function createPlugin( + // +- // If you want to use a production ready resolver you can switch to the ++ // If you want to use a production ready resolver you can switch to + // the one that is commented out below, it looks up a user entity in the +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +index 7ce5fcf..a12fee2 100644 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ b/packages/backend/src/plugins/scaffolder.ts +@@ -18,2 +18,4 @@ export default async function createPlugin( + catalogClient, ++ identity: env.identity, ++ permissions: env.permissions, + }); +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +index 8e0a864..9cd2c74 100644 +--- a/packages/backend/src/types.ts ++++ b/packages/backend/src/types.ts +@@ -11,2 +11,3 @@ import { PluginTaskScheduler } from '@backstage/backend-tasks'; + import { PermissionEvaluator } from '@backstage/plugin-permission-common'; ++import { IdentityApi } from '@backstage/plugin-auth-node'; + +@@ -22,2 +23,3 @@ export type PluginEnvironment = { + permissions: PermissionEvaluator; ++ identity: IdentityApi; + }; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..37c7fb1 +--- /dev/null ++++ b/playwright.config.ts +@@ -0,0 +1,60 @@ ++/* ++ * Copyright 2023 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { defineConfig } from '@playwright/test'; ++import { generateProjects } from '@backstage/e2e-test-utils/playwright'; ++ ++/** ++ * See https://playwright.dev/docs/test-configuration. ++ */ ++export default defineConfig({ ++ timeout: 60_000, ++ ++ expect: { ++ timeout: 5_000, ++ }, ++ ++ // Run your local dev server before starting the tests ++ webServer: process.env.CI ++ ? [] ++ : [ ++ { ++ command: 'yarn start', ++ port: 3000, ++ reuseExistingServer: true, ++ timeout: 60_000, ++ }, ++ ], ++ ++ forbidOnly: !!process.env.CI, ++ ++ retries: process.env.CI ? 2 : 0, ++ ++ reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]], ++ ++ use: { ++ actionTimeout: 0, ++ baseURL: ++ process.env.PLAYWRIGHT_URL ?? ++ (process.env.CI ? 'http://localhost:7007' : 'http://localhost:3000'), ++ screenshot: 'only-on-failure', ++ trace: 'on-first-retry', ++ }, ++ ++ outputDir: 'node_modules/.cache/e2e-test-results', ++ ++ projects: generateProjects(), // Find all packages with e2e-test folders ++}); +diff --git a/plugins/README.md b/plugins/README.md +index 58dc32c..d7865fd 100644 +--- a/plugins/README.md ++++ b/plugins/README.md +@@ -6,3 +6,3 @@ separate folder of its own. + If you want to create a new plugin here, go to your project root directory, run +-the command `yarn backstage-cli create`, and follow the on-screen instructions. ++the command `yarn new`, and follow the on-screen instructions. + +diff --git a/yarn.lock b/yarn.lock +new file mode 100644 +index 0000000..5ad7fe2 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1 @@ ++# intentionally left empty diff --git a/diffs/0.4.31..0.5.7-next.1.diff b/diffs/0.4.31..0.5.7-next.1.diff new file mode 100644 index 00000000..0cdd5183 --- /dev/null +++ b/diffs/0.4.31..0.5.7-next.1.diff @@ -0,0 +1,891 @@ +diff --git a/.eslintignore b/.eslintignore +new file mode 100644 +index 0000000..e5b1994 +--- /dev/null ++++ b/.eslintignore +@@ -0,0 +1 @@ ++playwright.config.ts +diff --git a/.gitignore b/.gitignore +index fdc2a5d..fbf8139 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -48 +48,7 @@ site + *-credentials.yaml ++ ++# vscode database functionality support files ++*.session.sql ++ ++# E2E test reports ++e2e-test-report/ +diff --git a/app-config.production.yaml b/app-config.production.yaml +index 6535d96..8f0751c 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -11,10 +11,5 @@ backend: + baseUrl: http://localhost:7007 +- listen: +- port: 7007 +- # The following host directive binds to all IPv4 interfaces when its value +- # is "0.0.0.0". This is the most permissive setting. The right value depends +- # on your specific deployment. If you remove the host line entirely, the +- # backend will bind on the interface that corresponds to the backend.baseUrl +- # hostname. +- host: 0.0.0.0 ++ # The listener can also be expressed as a single : string. In this case we bind to ++ # all interfaces, the most permissive setting. The right value depends on your specific deployment. ++ listen: ':7007' + +@@ -37,3 +32,3 @@ catalog: + # Overrides the default list locations from app-config.yaml as these contain example data. +- # See https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog for more details ++ # See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details + # on how to get entities into the catalog. +diff --git a/app-config.yaml b/app-config.yaml +index 4a058de..08d2abf 100644 +--- a/app-config.yaml ++++ b/app-config.yaml +@@ -9,3 +9,3 @@ backend: + # Used for enabling authentication, secret is shared by all backend plugins +- # See https://backstage.io/docs/tutorials/backend-to-backend-auth for ++ # See https://backstage.io/docs/auth/service-to-service-auth for + # information on the format +@@ -17,5 +17,4 @@ backend: + port: 7007 +- # Uncomment the following host directive to bind to all IPv4 interfaces and +- # not just the baseUrl hostname. +- # host: 0.0.0.0 ++ # Uncomment the following host directive to bind to specific interfaces ++ # host: 127.0.0.1 + csp: +@@ -33,4 +32,2 @@ backend: + connection: ':memory:' +- cache: +- store: memory + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir +@@ -49,5 +46,8 @@ integrations: + proxy: +- '/test': +- target: 'https://example.com' +- changeOrigin: true ++ ### Example for how to add a proxy endpoint for the frontend. ++ ### A typical reason to do this is to handle HTTPS and CORS for internal services. ++ # endpoints: ++ # '/test': ++ # target: 'https://example.com' ++ # changeOrigin: true + +diff --git a/backstage.json b/backstage.json +index d5d8ca8..707af29 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "1.6.0" ++ "version": "1.20.0-next.1" + } +diff --git a/examples/template/template.yaml b/examples/template/template.yaml +index 50052b7..33f262b 100644 +--- a/examples/template/template.yaml ++++ b/examples/template/template.yaml +@@ -63,3 +63,3 @@ spec: + input: +- repoContentsUrl: ${{ steps.publish.output.repoContentsUrl }} ++ repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} + catalogInfoPath: '/catalog-info.yaml' +@@ -70,5 +70,5 @@ spec: + - title: Repository +- url: ${{ steps.publish.output.remoteUrl }} ++ url: ${{ steps['publish'].output.remoteUrl }} + - title: Open in catalog + icon: catalog +- entityRef: ${{ steps.register.output.entityRef }} ++ entityRef: ${{ steps['register'].output.entityRef }} +diff --git a/lerna.json b/lerna.json +index 322929d..529a62f 100644 +--- a/lerna.json ++++ b/lerna.json +@@ -3,4 +3,4 @@ + "npmClient": "yarn", +- "useWorkspaces": true, +- "version": "0.1.0" ++ "version": "0.1.0", ++ "$schema": "node_modules/lerna/schemas/lerna-schema.json" + } +diff --git a/package.json b/package.json +index 05b423f..2364da8 100644 +--- a/package.json ++++ b/package.json +@@ -5,3 +5,3 @@ + "engines": { +- "node": "14 || 16" ++ "node": "18 || 20" + }, +@@ -11,3 +11,4 @@ + "start-backend": "yarn workspace backend start", +- "build": "backstage-cli repo build --all", ++ "build:backend": "yarn workspace backend build", ++ "build:all": "backstage-cli repo build --all", + "build-image": "yarn workspace backend build-image", +@@ -16,4 +17,6 @@ + "clean": "backstage-cli repo clean", +- "test": "backstage-cli test", +- "test:all": "lerna run test -- --coverage", ++ "test": "backstage-cli repo test", ++ "test:all": "backstage-cli repo test --coverage", ++ "test:e2e": "playwright test", ++ "fix": "backstage-cli repo fix", + "lint": "backstage-cli repo lint --since origin/master", +@@ -21,3 +24,2 @@ + "prettier:check": "prettier --check .", +- "create-plugin": "backstage-cli create-plugin --scope internal", + "new": "backstage-cli new --scope internal" +@@ -31,9 +33,11 @@ + "devDependencies": { +- "@backstage/cli": "^0.19.0", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/e2e-test-utils": "^0.1.0", ++ "@playwright/test": "^1.32.3", + "@spotify/prettier-config": "^12.0.0", +- "concurrently": "^6.0.0", +- "lerna": "^4.0.0", ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^9.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.6.4", +- "node-gyp": "^9.0.0" ++ "typescript": "~5.2.0" + }, +diff --git a/packages/app/.eslintignore b/packages/app/.eslintignore +new file mode 100644 +index 0000000..a48cf0d +--- /dev/null ++++ b/packages/app/.eslintignore +@@ -0,0 +1 @@ ++public +diff --git a/packages/app/cypress.json b/packages/app/cypress.json +deleted file mode 100644 +index 0cb845a..0000000 +--- a/packages/app/cypress.json ++++ /dev/null +@@ -1,6 +0,0 @@ +-{ +- "baseUrl": "http://localhost:3001", +- "fixturesFolder": false, +- "pluginsFile": false, +- "retries": 3 +-} +diff --git a/packages/app/cypress/.eslintrc.json b/packages/app/cypress/.eslintrc.json +deleted file mode 100644 +index 2b3a458..0000000 +--- a/packages/app/cypress/.eslintrc.json ++++ /dev/null +@@ -1,21 +0,0 @@ +-{ +- "plugins": ["cypress"], +- "extends": ["plugin:cypress/recommended"], +- "rules": { +- "jest/expect-expect": [ +- "error", +- { +- "assertFunctionNames": ["expect", "cy.contains"] +- } +- ], +- "import/no-extraneous-dependencies": [ +- "error", +- { +- "devDependencies": true, +- "optionalDependencies": true, +- "peerDependencies": true, +- "bundledDependencies": true +- } +- ] +- } +-} +diff --git a/packages/app/cypress/integration/app.js b/packages/app/cypress/integration/app.js +deleted file mode 100644 +index 43fb2e3..0000000 +--- a/packages/app/cypress/integration/app.js ++++ /dev/null +@@ -1,6 +0,0 @@ +-describe('App', () => { +- it('should render the catalog', () => { +- cy.visit('/'); +- cy.contains('My Company Catalog'); +- }); +-}); +diff --git a/packages/app/e2e-tests/app.test.ts b/packages/app/e2e-tests/app.test.ts +new file mode 100644 +index 0000000..d45bc0d +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,23 @@ ++/* ++ * Copyright 2020 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { test, expect } from '@playwright/test'; ++ ++test('App should render the welcome page', async ({ page }) => { ++ await page.goto('/'); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 522fad8..2100405 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -8,28 +8,35 @@ + }, ++ "scripts": { ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "clean": "backstage-cli package clean", ++ "test": "backstage-cli package test", ++ "lint": "backstage-cli package lint" ++ }, + "dependencies": { +- "@backstage/app-defaults": "^1.0.6", +- "@backstage/catalog-model": "^1.1.1", +- "@backstage/cli": "^0.19.0", +- "@backstage/core-app-api": "^1.1.0", +- "@backstage/core-components": "^0.11.1", +- "@backstage/core-plugin-api": "^1.0.6", +- "@backstage/integration-react": "^1.1.4", +- "@backstage/plugin-api-docs": "^0.8.9", +- "@backstage/plugin-catalog": "^1.5.1", +- "@backstage/plugin-catalog-common": "^1.0.6", +- "@backstage/plugin-catalog-graph": "^0.2.21", +- "@backstage/plugin-catalog-import": "^0.8.12", +- "@backstage/plugin-catalog-react": "^1.1.4", +- "@backstage/plugin-github-actions": "^0.5.9", +- "@backstage/plugin-org": "^0.5.9", +- "@backstage/plugin-permission-react": "^0.4.5", +- "@backstage/plugin-scaffolder": "^1.6.0", +- "@backstage/plugin-search": "^1.0.2", +- "@backstage/plugin-search-react": "^1.1.0", +- "@backstage/plugin-tech-radar": "^0.5.16", +- "@backstage/plugin-techdocs": "^1.3.2", +- "@backstage/plugin-techdocs-react": "^1.0.4", +- "@backstage/plugin-techdocs-module-addons-contrib": "^1.0.4", +- "@backstage/plugin-user-settings": "^0.4.8", +- "@backstage/theme": "^0.2.16", ++ "@backstage/app-defaults": "^1.4.5-next.1", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/core-app-api": "^1.11.1-next.0", ++ "@backstage/core-components": "^0.13.8-next.1", ++ "@backstage/core-plugin-api": "^1.8.0-next.0", ++ "@backstage/integration-react": "^1.1.21-next.1", ++ "@backstage/plugin-api-docs": "^0.9.13-next.1", ++ "@backstage/plugin-catalog": "^1.15.0-next.1", ++ "@backstage/plugin-catalog-common": "^1.0.17", ++ "@backstage/plugin-catalog-graph": "^0.2.38-next.1", ++ "@backstage/plugin-catalog-import": "^0.10.2-next.1", ++ "@backstage/plugin-catalog-react": "^1.9.0-next.1", ++ "@backstage/plugin-github-actions": "^0.6.7-next.1", ++ "@backstage/plugin-org": "^0.6.16-next.1", ++ "@backstage/plugin-permission-react": "^0.4.17-next.0", ++ "@backstage/plugin-scaffolder": "^1.16.0-next.1", ++ "@backstage/plugin-search": "^1.4.2-next.1", ++ "@backstage/plugin-search-react": "^1.7.2-next.1", ++ "@backstage/plugin-tech-radar": "^0.6.10-next.1", ++ "@backstage/plugin-techdocs": "^1.8.1-next.1", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.2-next.1", ++ "@backstage/plugin-techdocs-react": "^1.1.13-next.1", ++ "@backstage/plugin-user-settings": "^0.7.12-next.1", ++ "@backstage/theme": "^0.4.4-next.0", + "@material-ui/core": "^4.12.2", +@@ -39,28 +46,15 @@ + "react-dom": "^17.0.2", +- "react-router": "6.0.0-beta.0", +- "react-router-dom": "6.0.0-beta.0", +- "react-use": "^15.3.3" ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^1.2.0", ++ "@backstage/test-utils": "^1.4.5-next.0", ++ "@playwright/test": "^1.32.3", + "@testing-library/jest-dom": "^5.10.1", + "@testing-library/react": "^12.1.3", +- "@testing-library/user-event": "^12.0.7", +- "@types/node": "^14.14.32", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^8.0.0", + "@types/react-dom": "*", +- "cross-env": "^7.0.0", +- "cypress": "^9.7.0", +- "eslint-plugin-cypress": "^2.10.3", +- "start-server-and-test": "^1.10.11" +- }, +- "scripts": { +- "start": "backstage-cli package start", +- "build": "backstage-cli package build", +- "clean": "backstage-cli package clean", +- "test": "backstage-cli package test", +- "lint": "backstage-cli package lint", +- "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", +- "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", +- "cy:dev": "cypress open", +- "cy:run": "cypress run --browser chrome" ++ "cross-env": "^7.0.0" + }, +diff --git a/packages/app/public/index.html b/packages/app/public/index.html +index a936c73..fc758ee 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -10,3 +10,2 @@ + /> +- + +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index c487726..8d62f29 100644 +--- a/packages/app/src/App.tsx ++++ b/packages/app/src/App.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { Navigate, Route } from 'react-router'; ++import { Navigate, Route } from 'react-router-dom'; + import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; +@@ -31,5 +31,5 @@ import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; + import { createApp } from '@backstage/app-defaults'; +-import { FlatRoutes } from '@backstage/core-app-api'; ++import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; + import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; +-import { PermissionedRoute } from '@backstage/plugin-permission-react'; ++import { RequirePermission } from '@backstage/plugin-permission-react'; + import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; +@@ -42,2 +42,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -48,2 +49,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -55,8 +57,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -83,6 +82,9 @@ const routes = ( + /> +- } ++ element={ ++ ++ ++ ++ } + /> +@@ -96,4 +98,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -103,5 +105,3 @@ const App = () => ( + +- ++ , + ); +- +-export default App; +diff --git a/packages/app/src/components/Root/LogoFull.tsx b/packages/app/src/components/Root/LogoFull.tsx +index c7b1c84..47e3b73 100644 +--- a/packages/app/src/components/Root/LogoFull.tsx ++++ b/packages/app/src/components/Root/LogoFull.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/LogoIcon.tsx b/packages/app/src/components/Root/LogoIcon.tsx +index 073cf6e..7eae8c7 100644 +--- a/packages/app/src/components/Root/LogoIcon.tsx ++++ b/packages/app/src/components/Root/LogoIcon.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx +index b1164a3..6768b48 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,19 +1,3 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React, { PropsWithChildren } from 'react'; +-import { Link, makeStyles } from '@material-ui/core'; ++import { makeStyles } from '@material-ui/core'; + import HomeIcon from '@material-ui/icons/Home'; +@@ -25,3 +9,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -41,2 +24,3 @@ import { + useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -66,9 +50,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +diff --git a/packages/app/src/components/Root/index.ts b/packages/app/src/components/Root/index.ts +index dff706f..3528881 100644 +--- a/packages/app/src/components/Root/index.ts ++++ b/packages/app/src/components/Root/index.ts +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + export { Root } from './Root'; +diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx +index d98153f..6722ea2 100644 +--- a/packages/app/src/components/catalog/EntityPage.tsx ++++ b/packages/app/src/components/catalog/EntityPage.tsx +@@ -1,16 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ + import React from 'react'; +@@ -42,2 +27,4 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +@@ -118,2 +105,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx +index 928b820..1788dde 100644 +--- a/packages/app/src/components/search/SearchPage.tsx ++++ b/packages/app/src/components/search/SearchPage.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; ++import { makeStyles, Theme, Grid, Paper } from '@material-ui/core'; + +@@ -12,3 +12,2 @@ import { SearchType } from '@backstage/plugin-search'; + import { +- DefaultResultListItem, + SearchBar, +@@ -16,2 +15,3 @@ import { + SearchResult, ++ SearchPagination, + useSearch, +@@ -111,38 +111,6 @@ const SearchPage = () => { + ++ + +- {({ results }) => ( +- +- {results.map(({ type, document, highlight, rank }) => { +- switch (type) { +- case 'software-catalog': +- return ( +- +- ); +- case 'techdocs': +- return ( +- +- ); +- default: +- return ( +- +- ); +- } +- })} +- +- )} ++ } /> ++ } /> + +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index 682798b..18548e9 100644 +--- a/packages/backend/Dockerfile ++++ b/packages/backend/Dockerfile +@@ -7,3 +7,3 @@ + # yarn tsc +-# yarn build ++# yarn build:backend + # +@@ -11,3 +11,10 @@ + +-FROM node:16-bullseye-slim ++FROM node:18-bookworm-slim ++ ++# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends python3 g++ build-essential && \ ++ yarn config set python /usr/bin/python3 + +@@ -15,6 +22,6 @@ FROM node:16-bullseye-slim + # in which case you should also move better-sqlite3 to "devDependencies" in package.json. +-RUN apt-get update && \ +- apt-get install -y --no-install-recommends libsqlite3-dev python3 build-essential && \ +- rm -rf /var/lib/apt/lists/* && \ +- yarn config set python /usr/bin/python3 ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends libsqlite3-dev + +@@ -22,2 +29,6 @@ RUN apt-get update && \ + USER node ++ ++# This should create the app dir as `node`. ++# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. ++# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. + WORKDIR /app +@@ -33,3 +44,4 @@ RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz + +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + +diff --git a/packages/backend/README.md b/packages/backend/README.md +index 02426ef..3607b0a 100644 +--- a/packages/backend/README.md ++++ b/packages/backend/README.md +@@ -18,4 +18,2 @@ To run the example backend, first go to the project root and run + yarn install +-yarn tsc +-yarn build + ``` +@@ -27,7 +25,2 @@ After that, go to the `packages/backend` directory and run + ```bash +-AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x \ +-AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x \ +-AUTH_OAUTH2_CLIENT_ID=x AUTH_OAUTH2_CLIENT_SECRET=x \ +-AUTH_OAUTH2_AUTH_URL=x AUTH_OAUTH2_TOKEN_URL=x \ +-LOG_LEVEL=debug \ + yarn start +@@ -35,4 +28,4 @@ yarn start + +-Substitute `x` for actual values, or leave them as dummy values just to try out +-the backend without using the auth or sentry features. ++If you want to override any configuration locally, for example adding any secrets, ++you can do so in `app-config.local.yaml`. + +@@ -45,3 +38,3 @@ locations to the backend. These are places where the backend can find some + entity descriptor data to consume and serve. For more information, see +-[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog). ++[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). + +@@ -65,2 +58,2 @@ and + - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +-- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) ++- [Backstage Documentation](https://backstage.io/docs) +diff --git a/packages/backend/package.json b/packages/backend/package.json +index 515c7a7..f224eaf 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -18,21 +18,21 @@ + "dependencies": { ++ "@backstage/backend-common": "^0.19.9-next.1", ++ "@backstage/backend-tasks": "^0.5.12-next.1", ++ "@backstage/catalog-client": "^1.4.5", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/config": "^1.1.1", ++ "@backstage/plugin-app-backend": "^0.3.55-next.1", ++ "@backstage/plugin-auth-backend": "^0.20.0-next.1", ++ "@backstage/plugin-auth-node": "^0.4.1-next.1", ++ "@backstage/plugin-catalog-backend": "^1.15.0-next.1", ++ "@backstage/plugin-permission-common": "^0.7.9", ++ "@backstage/plugin-permission-node": "^0.7.18-next.1", ++ "@backstage/plugin-proxy-backend": "^0.4.5-next.1", ++ "@backstage/plugin-scaffolder-backend": "^1.19.0-next.1", ++ "@backstage/plugin-search-backend": "^1.4.7-next.1", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.16-next.1", ++ "@backstage/plugin-search-backend-node": "^1.2.11-next.1", ++ "@backstage/plugin-techdocs-backend": "^1.9.0-next.1", + "app": "link:../app", +- "@backstage/backend-common": "^0.15.1", +- "@backstage/backend-tasks": "^0.3.5", +- "@backstage/catalog-model": "^1.1.1", +- "@backstage/catalog-client": "^1.1.0", +- "@backstage/config": "^1.0.2", +- "@backstage/plugin-app-backend": "^0.3.36", +- "@backstage/plugin-auth-backend": "^0.16.0", +- "@backstage/plugin-auth-node": "^0.2.5", +- "@backstage/plugin-catalog-backend": "^1.4.0", +- "@backstage/plugin-permission-common": "^0.6.4", +- "@backstage/plugin-permission-node": "^0.6.5", +- "@backstage/plugin-proxy-backend": "^0.2.30", +- "@backstage/plugin-scaffolder-backend": "^1.6.0", +- "@backstage/plugin-search-backend": "^1.0.2", +- "@backstage/plugin-search-backend-module-pg": "^0.4.0", +- "@backstage/plugin-search-backend-node": "^1.0.2", +- "@backstage/plugin-techdocs-backend": "^1.3.0", +- "better-sqlite3": "^7.5.0", ++ "better-sqlite3": "^8.0.0", + "dockerode": "^3.3.1", +@@ -44,6 +44,6 @@ + "devDependencies": { +- "@backstage/cli": "^0.19.0", ++ "@backstage/cli": "^0.24.0-next.1", + "@types/dockerode": "^3.3.0", +- "@types/express-serve-static-core": "^4.17.5", + "@types/express": "^4.17.6", ++ "@types/express-serve-static-core": "^4.17.5", + "@types/luxon": "^2.0.4" +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index c4736a5..04c4ff9 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -17,3 +17,3 @@ import { + DatabaseManager, +- SingleHostDiscovery, ++ HostDiscovery, + UrlReaders, +@@ -37,3 +37,3 @@ function makeCreateEnv(config: Config) { + const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++ const discovery = HostDiscovery.fromConfig(config); + const cacheManager = CacheManager.fromConfig(config); +@@ -41,3 +41,3 @@ function makeCreateEnv(config: Config) { + const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); ++ const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); + +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +index ef46f07..a12fee2 100644 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ b/packages/backend/src/plugins/scaffolder.ts +@@ -19,2 +19,3 @@ export default async function createPlugin( + identity: env.identity, ++ permissions: env.permissions, + }); +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..37c7fb1 +--- /dev/null ++++ b/playwright.config.ts +@@ -0,0 +1,60 @@ ++/* ++ * Copyright 2023 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { defineConfig } from '@playwright/test'; ++import { generateProjects } from '@backstage/e2e-test-utils/playwright'; ++ ++/** ++ * See https://playwright.dev/docs/test-configuration. ++ */ ++export default defineConfig({ ++ timeout: 60_000, ++ ++ expect: { ++ timeout: 5_000, ++ }, ++ ++ // Run your local dev server before starting the tests ++ webServer: process.env.CI ++ ? [] ++ : [ ++ { ++ command: 'yarn start', ++ port: 3000, ++ reuseExistingServer: true, ++ timeout: 60_000, ++ }, ++ ], ++ ++ forbidOnly: !!process.env.CI, ++ ++ retries: process.env.CI ? 2 : 0, ++ ++ reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]], ++ ++ use: { ++ actionTimeout: 0, ++ baseURL: ++ process.env.PLAYWRIGHT_URL ?? ++ (process.env.CI ? 'http://localhost:7007' : 'http://localhost:3000'), ++ screenshot: 'only-on-failure', ++ trace: 'on-first-retry', ++ }, ++ ++ outputDir: 'node_modules/.cache/e2e-test-results', ++ ++ projects: generateProjects(), // Find all packages with e2e-test folders ++}); +diff --git a/plugins/README.md b/plugins/README.md +index 58dc32c..d7865fd 100644 +--- a/plugins/README.md ++++ b/plugins/README.md +@@ -6,3 +6,3 @@ separate folder of its own. + If you want to create a new plugin here, go to your project root directory, run +-the command `yarn backstage-cli create`, and follow the on-screen instructions. ++the command `yarn new`, and follow the on-screen instructions. + +diff --git a/yarn.lock b/yarn.lock +new file mode 100644 +index 0000000..5ad7fe2 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1 @@ ++# intentionally left empty diff --git a/diffs/0.4.32..0.5.7-next.1.diff b/diffs/0.4.32..0.5.7-next.1.diff new file mode 100644 index 00000000..fdad41c0 --- /dev/null +++ b/diffs/0.4.32..0.5.7-next.1.diff @@ -0,0 +1,714 @@ +diff --git a/.eslintignore b/.eslintignore +new file mode 100644 +index 0000000..e5b1994 +--- /dev/null ++++ b/.eslintignore +@@ -0,0 +1 @@ ++playwright.config.ts +diff --git a/.gitignore b/.gitignore +index d452ac2..fbf8139 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -51 +51,4 @@ site + *.session.sql ++ ++# E2E test reports ++e2e-test-report/ +diff --git a/app-config.production.yaml b/app-config.production.yaml +index 6535d96..8f0751c 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -11,10 +11,5 @@ backend: + baseUrl: http://localhost:7007 +- listen: +- port: 7007 +- # The following host directive binds to all IPv4 interfaces when its value +- # is "0.0.0.0". This is the most permissive setting. The right value depends +- # on your specific deployment. If you remove the host line entirely, the +- # backend will bind on the interface that corresponds to the backend.baseUrl +- # hostname. +- host: 0.0.0.0 ++ # The listener can also be expressed as a single : string. In this case we bind to ++ # all interfaces, the most permissive setting. The right value depends on your specific deployment. ++ listen: ':7007' + +@@ -37,3 +32,3 @@ catalog: + # Overrides the default list locations from app-config.yaml as these contain example data. +- # See https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog for more details ++ # See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details + # on how to get entities into the catalog. +diff --git a/app-config.yaml b/app-config.yaml +index 4a058de..08d2abf 100644 +--- a/app-config.yaml ++++ b/app-config.yaml +@@ -9,3 +9,3 @@ backend: + # Used for enabling authentication, secret is shared by all backend plugins +- # See https://backstage.io/docs/tutorials/backend-to-backend-auth for ++ # See https://backstage.io/docs/auth/service-to-service-auth for + # information on the format +@@ -17,5 +17,4 @@ backend: + port: 7007 +- # Uncomment the following host directive to bind to all IPv4 interfaces and +- # not just the baseUrl hostname. +- # host: 0.0.0.0 ++ # Uncomment the following host directive to bind to specific interfaces ++ # host: 127.0.0.1 + csp: +@@ -33,4 +32,2 @@ backend: + connection: ':memory:' +- cache: +- store: memory + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir +@@ -49,5 +46,8 @@ integrations: + proxy: +- '/test': +- target: 'https://example.com' +- changeOrigin: true ++ ### Example for how to add a proxy endpoint for the frontend. ++ ### A typical reason to do this is to handle HTTPS and CORS for internal services. ++ # endpoints: ++ # '/test': ++ # target: 'https://example.com' ++ # changeOrigin: true + +diff --git a/backstage.json b/backstage.json +index 6076f7d..707af29 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "1.7.0" ++ "version": "1.20.0-next.1" + } +diff --git a/examples/template/template.yaml b/examples/template/template.yaml +index 50052b7..33f262b 100644 +--- a/examples/template/template.yaml ++++ b/examples/template/template.yaml +@@ -63,3 +63,3 @@ spec: + input: +- repoContentsUrl: ${{ steps.publish.output.repoContentsUrl }} ++ repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} + catalogInfoPath: '/catalog-info.yaml' +@@ -70,5 +70,5 @@ spec: + - title: Repository +- url: ${{ steps.publish.output.remoteUrl }} ++ url: ${{ steps['publish'].output.remoteUrl }} + - title: Open in catalog + icon: catalog +- entityRef: ${{ steps.register.output.entityRef }} ++ entityRef: ${{ steps['register'].output.entityRef }} +diff --git a/lerna.json b/lerna.json +index 322929d..529a62f 100644 +--- a/lerna.json ++++ b/lerna.json +@@ -3,4 +3,4 @@ + "npmClient": "yarn", +- "useWorkspaces": true, +- "version": "0.1.0" ++ "version": "0.1.0", ++ "$schema": "node_modules/lerna/schemas/lerna-schema.json" + } +diff --git a/package.json b/package.json +index 7a27e83..2364da8 100644 +--- a/package.json ++++ b/package.json +@@ -5,3 +5,3 @@ + "engines": { +- "node": "14 || 16" ++ "node": "18 || 20" + }, +@@ -11,3 +11,4 @@ + "start-backend": "yarn workspace backend start", +- "build": "backstage-cli repo build --all", ++ "build:backend": "yarn workspace backend build", ++ "build:all": "backstage-cli repo build --all", + "build-image": "yarn workspace backend build-image", +@@ -18,2 +19,4 @@ + "test:all": "backstage-cli repo test --coverage", ++ "test:e2e": "playwright test", ++ "fix": "backstage-cli repo fix", + "lint": "backstage-cli repo lint --since origin/master", +@@ -21,3 +24,2 @@ + "prettier:check": "prettier --check .", +- "create-plugin": "backstage-cli create-plugin --scope internal", + "new": "backstage-cli new --scope internal" +@@ -31,9 +33,11 @@ + "devDependencies": { +- "@backstage/cli": "^0.20.0", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/e2e-test-utils": "^0.1.0", ++ "@playwright/test": "^1.32.3", + "@spotify/prettier-config": "^12.0.0", +- "concurrently": "^6.0.0", +- "lerna": "^4.0.0", ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^9.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.6.4", +- "node-gyp": "^9.0.0" ++ "typescript": "~5.2.0" + }, +diff --git a/packages/app/.eslintignore b/packages/app/.eslintignore +new file mode 100644 +index 0000000..a48cf0d +--- /dev/null ++++ b/packages/app/.eslintignore +@@ -0,0 +1 @@ ++public +diff --git a/packages/app/cypress.json b/packages/app/cypress.json +deleted file mode 100644 +index 0cb845a..0000000 +--- a/packages/app/cypress.json ++++ /dev/null +@@ -1,6 +0,0 @@ +-{ +- "baseUrl": "http://localhost:3001", +- "fixturesFolder": false, +- "pluginsFile": false, +- "retries": 3 +-} +diff --git a/packages/app/cypress/.eslintrc.json b/packages/app/cypress/.eslintrc.json +deleted file mode 100644 +index 2b3a458..0000000 +--- a/packages/app/cypress/.eslintrc.json ++++ /dev/null +@@ -1,21 +0,0 @@ +-{ +- "plugins": ["cypress"], +- "extends": ["plugin:cypress/recommended"], +- "rules": { +- "jest/expect-expect": [ +- "error", +- { +- "assertFunctionNames": ["expect", "cy.contains"] +- } +- ], +- "import/no-extraneous-dependencies": [ +- "error", +- { +- "devDependencies": true, +- "optionalDependencies": true, +- "peerDependencies": true, +- "bundledDependencies": true +- } +- ] +- } +-} +diff --git a/packages/app/cypress/integration/app.js b/packages/app/cypress/integration/app.js +deleted file mode 100644 +index 43fb2e3..0000000 +--- a/packages/app/cypress/integration/app.js ++++ /dev/null +@@ -1,6 +0,0 @@ +-describe('App', () => { +- it('should render the catalog', () => { +- cy.visit('/'); +- cy.contains('My Company Catalog'); +- }); +-}); +diff --git a/packages/app/e2e-tests/app.test.ts b/packages/app/e2e-tests/app.test.ts +new file mode 100644 +index 0000000..d45bc0d +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,23 @@ ++/* ++ * Copyright 2020 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { test, expect } from '@playwright/test'; ++ ++test('App should render the welcome page', async ({ page }) => { ++ await page.goto('/'); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index dd1b831..2100405 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -13,34 +13,30 @@ + "test": "backstage-cli package test", +- "lint": "backstage-cli package lint", +- "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", +- "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", +- "cy:dev": "cypress open", +- "cy:run": "cypress run --browser chrome" ++ "lint": "backstage-cli package lint" + }, + "dependencies": { +- "@backstage/app-defaults": "^1.0.7", +- "@backstage/catalog-model": "^1.1.2", +- "@backstage/cli": "^0.20.0", +- "@backstage/core-app-api": "^1.1.1", +- "@backstage/core-components": "^0.11.2", +- "@backstage/core-plugin-api": "^1.0.7", +- "@backstage/integration-react": "^1.1.5", +- "@backstage/plugin-api-docs": "^0.8.10", +- "@backstage/plugin-catalog": "^1.6.0", +- "@backstage/plugin-catalog-common": "^1.0.7", +- "@backstage/plugin-catalog-graph": "^0.2.22", +- "@backstage/plugin-catalog-import": "^0.9.0", +- "@backstage/plugin-catalog-react": "^1.2.0", +- "@backstage/plugin-github-actions": "^0.5.10", +- "@backstage/plugin-org": "^0.5.10", +- "@backstage/plugin-permission-react": "^0.4.6", +- "@backstage/plugin-scaffolder": "^1.7.0", +- "@backstage/plugin-search": "^1.0.3", +- "@backstage/plugin-search-react": "^1.2.0", +- "@backstage/plugin-tech-radar": "^0.5.17", +- "@backstage/plugin-techdocs": "^1.3.3", +- "@backstage/plugin-techdocs-react": "^1.0.5", +- "@backstage/plugin-techdocs-module-addons-contrib": "^1.0.5", +- "@backstage/plugin-user-settings": "^0.5.0", +- "@backstage/theme": "^0.2.16", ++ "@backstage/app-defaults": "^1.4.5-next.1", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/core-app-api": "^1.11.1-next.0", ++ "@backstage/core-components": "^0.13.8-next.1", ++ "@backstage/core-plugin-api": "^1.8.0-next.0", ++ "@backstage/integration-react": "^1.1.21-next.1", ++ "@backstage/plugin-api-docs": "^0.9.13-next.1", ++ "@backstage/plugin-catalog": "^1.15.0-next.1", ++ "@backstage/plugin-catalog-common": "^1.0.17", ++ "@backstage/plugin-catalog-graph": "^0.2.38-next.1", ++ "@backstage/plugin-catalog-import": "^0.10.2-next.1", ++ "@backstage/plugin-catalog-react": "^1.9.0-next.1", ++ "@backstage/plugin-github-actions": "^0.6.7-next.1", ++ "@backstage/plugin-org": "^0.6.16-next.1", ++ "@backstage/plugin-permission-react": "^0.4.17-next.0", ++ "@backstage/plugin-scaffolder": "^1.16.0-next.1", ++ "@backstage/plugin-search": "^1.4.2-next.1", ++ "@backstage/plugin-search-react": "^1.7.2-next.1", ++ "@backstage/plugin-tech-radar": "^0.6.10-next.1", ++ "@backstage/plugin-techdocs": "^1.8.1-next.1", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.2-next.1", ++ "@backstage/plugin-techdocs-react": "^1.1.13-next.1", ++ "@backstage/plugin-user-settings": "^0.7.12-next.1", ++ "@backstage/theme": "^0.4.4-next.0", + "@material-ui/core": "^4.12.2", +@@ -55,3 +51,4 @@ + "devDependencies": { +- "@backstage/test-utils": "^1.2.1", ++ "@backstage/test-utils": "^1.4.5-next.0", ++ "@playwright/test": "^1.32.3", + "@testing-library/jest-dom": "^5.10.1", +@@ -59,8 +56,5 @@ + "@testing-library/user-event": "^14.0.0", +- "@types/node": "^16.11.26", ++ "@testing-library/dom": "^8.0.0", + "@types/react-dom": "*", +- "cross-env": "^7.0.0", +- "cypress": "^9.7.0", +- "eslint-plugin-cypress": "^2.10.3", +- "start-server-and-test": "^1.10.11" ++ "cross-env": "^7.0.0" + }, +diff --git a/packages/app/public/index.html b/packages/app/public/index.html +index a936c73..fc758ee 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -10,3 +10,2 @@ + /> +- + +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 46cb786..8d62f29 100644 +--- a/packages/app/src/App.tsx ++++ b/packages/app/src/App.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { Navigate, Route } from 'react-router'; ++import { Navigate, Route } from 'react-router-dom'; + import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; +@@ -31,3 +31,3 @@ import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; + import { createApp } from '@backstage/app-defaults'; +-import { FlatRoutes } from '@backstage/core-app-api'; ++import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; + import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; +@@ -42,2 +42,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -48,2 +49,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -55,5 +57,2 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( +@@ -99,4 +98,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -106,5 +105,3 @@ const App = () => ( + +- ++ , + ); +- +-export default App; +diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx +index 5400421..6768b48 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,3 +1,3 @@ + import React, { PropsWithChildren } from 'react'; +-import { Link, makeStyles } from '@material-ui/core'; ++import { makeStyles } from '@material-ui/core'; + import HomeIcon from '@material-ui/icons/Home'; +@@ -9,3 +9,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -25,2 +24,3 @@ import { + useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -50,9 +50,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx +index 54a05ee..6722ea2 100644 +--- a/packages/app/src/components/catalog/EntityPage.tsx ++++ b/packages/app/src/components/catalog/EntityPage.tsx +@@ -27,2 +27,4 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +@@ -103,2 +105,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx +index 9f11d0c..1788dde 100644 +--- a/packages/app/src/components/search/SearchPage.tsx ++++ b/packages/app/src/components/search/SearchPage.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; ++import { makeStyles, Theme, Grid, Paper } from '@material-ui/core'; + +@@ -12,3 +12,2 @@ import { SearchType } from '@backstage/plugin-search'; + import { +- DefaultResultListItem, + SearchBar, +@@ -114,37 +113,4 @@ const SearchPage = () => { + +- {({ results }) => ( +- +- {results.map(({ type, document, highlight, rank }) => { +- switch (type) { +- case 'software-catalog': +- return ( +- +- ); +- case 'techdocs': +- return ( +- +- ); +- default: +- return ( +- +- ); +- } +- })} +- +- )} ++ } /> ++ } /> + +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index f0be3ac..18548e9 100644 +--- a/packages/backend/Dockerfile ++++ b/packages/backend/Dockerfile +@@ -7,3 +7,3 @@ + # yarn tsc +-# yarn build ++# yarn build:backend + # +@@ -11,3 +11,10 @@ + +-FROM node:16-bullseye-slim ++FROM node:18-bookworm-slim ++ ++# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends python3 g++ build-essential && \ ++ yarn config set python /usr/bin/python3 + +@@ -18,4 +25,3 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + apt-get update && \ +- apt-get install -y --no-install-recommends libsqlite3-dev python3 build-essential && \ +- yarn config set python /usr/bin/python3 ++ apt-get install -y --no-install-recommends libsqlite3-dev + +diff --git a/packages/backend/README.md b/packages/backend/README.md +index 02426ef..3607b0a 100644 +--- a/packages/backend/README.md ++++ b/packages/backend/README.md +@@ -18,4 +18,2 @@ To run the example backend, first go to the project root and run + yarn install +-yarn tsc +-yarn build + ``` +@@ -27,7 +25,2 @@ After that, go to the `packages/backend` directory and run + ```bash +-AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x \ +-AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x \ +-AUTH_OAUTH2_CLIENT_ID=x AUTH_OAUTH2_CLIENT_SECRET=x \ +-AUTH_OAUTH2_AUTH_URL=x AUTH_OAUTH2_TOKEN_URL=x \ +-LOG_LEVEL=debug \ + yarn start +@@ -35,4 +28,4 @@ yarn start + +-Substitute `x` for actual values, or leave them as dummy values just to try out +-the backend without using the auth or sentry features. ++If you want to override any configuration locally, for example adding any secrets, ++you can do so in `app-config.local.yaml`. + +@@ -45,3 +38,3 @@ locations to the backend. These are places where the backend can find some + entity descriptor data to consume and serve. For more information, see +-[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog). ++[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). + +@@ -65,2 +58,2 @@ and + - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +-- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) ++- [Backstage Documentation](https://backstage.io/docs) +diff --git a/packages/backend/package.json b/packages/backend/package.json +index 7154350..f224eaf 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -18,21 +18,21 @@ + "dependencies": { ++ "@backstage/backend-common": "^0.19.9-next.1", ++ "@backstage/backend-tasks": "^0.5.12-next.1", ++ "@backstage/catalog-client": "^1.4.5", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/config": "^1.1.1", ++ "@backstage/plugin-app-backend": "^0.3.55-next.1", ++ "@backstage/plugin-auth-backend": "^0.20.0-next.1", ++ "@backstage/plugin-auth-node": "^0.4.1-next.1", ++ "@backstage/plugin-catalog-backend": "^1.15.0-next.1", ++ "@backstage/plugin-permission-common": "^0.7.9", ++ "@backstage/plugin-permission-node": "^0.7.18-next.1", ++ "@backstage/plugin-proxy-backend": "^0.4.5-next.1", ++ "@backstage/plugin-scaffolder-backend": "^1.19.0-next.1", ++ "@backstage/plugin-search-backend": "^1.4.7-next.1", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.16-next.1", ++ "@backstage/plugin-search-backend-node": "^1.2.11-next.1", ++ "@backstage/plugin-techdocs-backend": "^1.9.0-next.1", + "app": "link:../app", +- "@backstage/backend-common": "^0.15.2", +- "@backstage/backend-tasks": "^0.3.6", +- "@backstage/catalog-model": "^1.1.2", +- "@backstage/catalog-client": "^1.1.1", +- "@backstage/config": "^1.0.3", +- "@backstage/plugin-app-backend": "^0.3.37", +- "@backstage/plugin-auth-backend": "^0.17.0", +- "@backstage/plugin-auth-node": "^0.2.6", +- "@backstage/plugin-catalog-backend": "^1.5.0", +- "@backstage/plugin-permission-common": "^0.7.0", +- "@backstage/plugin-permission-node": "^0.7.0", +- "@backstage/plugin-proxy-backend": "^0.2.31", +- "@backstage/plugin-scaffolder-backend": "^1.7.0", +- "@backstage/plugin-search-backend": "^1.1.0", +- "@backstage/plugin-search-backend-module-pg": "^0.4.1", +- "@backstage/plugin-search-backend-node": "^1.0.3", +- "@backstage/plugin-techdocs-backend": "^1.4.0", +- "better-sqlite3": "^7.5.0", ++ "better-sqlite3": "^8.0.0", + "dockerode": "^3.3.1", +@@ -44,6 +44,6 @@ + "devDependencies": { +- "@backstage/cli": "^0.20.0", ++ "@backstage/cli": "^0.24.0-next.1", + "@types/dockerode": "^3.3.0", +- "@types/express-serve-static-core": "^4.17.5", + "@types/express": "^4.17.6", ++ "@types/express-serve-static-core": "^4.17.5", + "@types/luxon": "^2.0.4" +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index c4736a5..04c4ff9 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -17,3 +17,3 @@ import { + DatabaseManager, +- SingleHostDiscovery, ++ HostDiscovery, + UrlReaders, +@@ -37,3 +37,3 @@ function makeCreateEnv(config: Config) { + const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++ const discovery = HostDiscovery.fromConfig(config); + const cacheManager = CacheManager.fromConfig(config); +@@ -41,3 +41,3 @@ function makeCreateEnv(config: Config) { + const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); ++ const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); + +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +index ef46f07..a12fee2 100644 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ b/packages/backend/src/plugins/scaffolder.ts +@@ -19,2 +19,3 @@ export default async function createPlugin( + identity: env.identity, ++ permissions: env.permissions, + }); +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..37c7fb1 +--- /dev/null ++++ b/playwright.config.ts +@@ -0,0 +1,60 @@ ++/* ++ * Copyright 2023 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { defineConfig } from '@playwright/test'; ++import { generateProjects } from '@backstage/e2e-test-utils/playwright'; ++ ++/** ++ * See https://playwright.dev/docs/test-configuration. ++ */ ++export default defineConfig({ ++ timeout: 60_000, ++ ++ expect: { ++ timeout: 5_000, ++ }, ++ ++ // Run your local dev server before starting the tests ++ webServer: process.env.CI ++ ? [] ++ : [ ++ { ++ command: 'yarn start', ++ port: 3000, ++ reuseExistingServer: true, ++ timeout: 60_000, ++ }, ++ ], ++ ++ forbidOnly: !!process.env.CI, ++ ++ retries: process.env.CI ? 2 : 0, ++ ++ reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]], ++ ++ use: { ++ actionTimeout: 0, ++ baseURL: ++ process.env.PLAYWRIGHT_URL ?? ++ (process.env.CI ? 'http://localhost:7007' : 'http://localhost:3000'), ++ screenshot: 'only-on-failure', ++ trace: 'on-first-retry', ++ }, ++ ++ outputDir: 'node_modules/.cache/e2e-test-results', ++ ++ projects: generateProjects(), // Find all packages with e2e-test folders ++}); +diff --git a/plugins/README.md b/plugins/README.md +index 58dc32c..d7865fd 100644 +--- a/plugins/README.md ++++ b/plugins/README.md +@@ -6,3 +6,3 @@ separate folder of its own. + If you want to create a new plugin here, go to your project root directory, run +-the command `yarn backstage-cli create`, and follow the on-screen instructions. ++the command `yarn new`, and follow the on-screen instructions. + diff --git a/diffs/0.4.33..0.5.7-next.1.diff b/diffs/0.4.33..0.5.7-next.1.diff new file mode 100644 index 00000000..4e8a3d5e --- /dev/null +++ b/diffs/0.4.33..0.5.7-next.1.diff @@ -0,0 +1,714 @@ +diff --git a/.eslintignore b/.eslintignore +new file mode 100644 +index 0000000..e5b1994 +--- /dev/null ++++ b/.eslintignore +@@ -0,0 +1 @@ ++playwright.config.ts +diff --git a/.gitignore b/.gitignore +index d452ac2..fbf8139 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -51 +51,4 @@ site + *.session.sql ++ ++# E2E test reports ++e2e-test-report/ +diff --git a/app-config.production.yaml b/app-config.production.yaml +index 6535d96..8f0751c 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -11,10 +11,5 @@ backend: + baseUrl: http://localhost:7007 +- listen: +- port: 7007 +- # The following host directive binds to all IPv4 interfaces when its value +- # is "0.0.0.0". This is the most permissive setting. The right value depends +- # on your specific deployment. If you remove the host line entirely, the +- # backend will bind on the interface that corresponds to the backend.baseUrl +- # hostname. +- host: 0.0.0.0 ++ # The listener can also be expressed as a single : string. In this case we bind to ++ # all interfaces, the most permissive setting. The right value depends on your specific deployment. ++ listen: ':7007' + +@@ -37,3 +32,3 @@ catalog: + # Overrides the default list locations from app-config.yaml as these contain example data. +- # See https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog for more details ++ # See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details + # on how to get entities into the catalog. +diff --git a/app-config.yaml b/app-config.yaml +index 4a058de..08d2abf 100644 +--- a/app-config.yaml ++++ b/app-config.yaml +@@ -9,3 +9,3 @@ backend: + # Used for enabling authentication, secret is shared by all backend plugins +- # See https://backstage.io/docs/tutorials/backend-to-backend-auth for ++ # See https://backstage.io/docs/auth/service-to-service-auth for + # information on the format +@@ -17,5 +17,4 @@ backend: + port: 7007 +- # Uncomment the following host directive to bind to all IPv4 interfaces and +- # not just the baseUrl hostname. +- # host: 0.0.0.0 ++ # Uncomment the following host directive to bind to specific interfaces ++ # host: 127.0.0.1 + csp: +@@ -33,4 +32,2 @@ backend: + connection: ':memory:' +- cache: +- store: memory + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir +@@ -49,5 +46,8 @@ integrations: + proxy: +- '/test': +- target: 'https://example.com' +- changeOrigin: true ++ ### Example for how to add a proxy endpoint for the frontend. ++ ### A typical reason to do this is to handle HTTPS and CORS for internal services. ++ # endpoints: ++ # '/test': ++ # target: 'https://example.com' ++ # changeOrigin: true + +diff --git a/backstage.json b/backstage.json +index 96bed22..707af29 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "1.7.1" ++ "version": "1.20.0-next.1" + } +diff --git a/examples/template/template.yaml b/examples/template/template.yaml +index 50052b7..33f262b 100644 +--- a/examples/template/template.yaml ++++ b/examples/template/template.yaml +@@ -63,3 +63,3 @@ spec: + input: +- repoContentsUrl: ${{ steps.publish.output.repoContentsUrl }} ++ repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} + catalogInfoPath: '/catalog-info.yaml' +@@ -70,5 +70,5 @@ spec: + - title: Repository +- url: ${{ steps.publish.output.remoteUrl }} ++ url: ${{ steps['publish'].output.remoteUrl }} + - title: Open in catalog + icon: catalog +- entityRef: ${{ steps.register.output.entityRef }} ++ entityRef: ${{ steps['register'].output.entityRef }} +diff --git a/lerna.json b/lerna.json +index 322929d..529a62f 100644 +--- a/lerna.json ++++ b/lerna.json +@@ -3,4 +3,4 @@ + "npmClient": "yarn", +- "useWorkspaces": true, +- "version": "0.1.0" ++ "version": "0.1.0", ++ "$schema": "node_modules/lerna/schemas/lerna-schema.json" + } +diff --git a/package.json b/package.json +index 7a27e83..2364da8 100644 +--- a/package.json ++++ b/package.json +@@ -5,3 +5,3 @@ + "engines": { +- "node": "14 || 16" ++ "node": "18 || 20" + }, +@@ -11,3 +11,4 @@ + "start-backend": "yarn workspace backend start", +- "build": "backstage-cli repo build --all", ++ "build:backend": "yarn workspace backend build", ++ "build:all": "backstage-cli repo build --all", + "build-image": "yarn workspace backend build-image", +@@ -18,2 +19,4 @@ + "test:all": "backstage-cli repo test --coverage", ++ "test:e2e": "playwright test", ++ "fix": "backstage-cli repo fix", + "lint": "backstage-cli repo lint --since origin/master", +@@ -21,3 +24,2 @@ + "prettier:check": "prettier --check .", +- "create-plugin": "backstage-cli create-plugin --scope internal", + "new": "backstage-cli new --scope internal" +@@ -31,9 +33,11 @@ + "devDependencies": { +- "@backstage/cli": "^0.20.0", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/e2e-test-utils": "^0.1.0", ++ "@playwright/test": "^1.32.3", + "@spotify/prettier-config": "^12.0.0", +- "concurrently": "^6.0.0", +- "lerna": "^4.0.0", ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^9.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.6.4", +- "node-gyp": "^9.0.0" ++ "typescript": "~5.2.0" + }, +diff --git a/packages/app/.eslintignore b/packages/app/.eslintignore +new file mode 100644 +index 0000000..a48cf0d +--- /dev/null ++++ b/packages/app/.eslintignore +@@ -0,0 +1 @@ ++public +diff --git a/packages/app/cypress.json b/packages/app/cypress.json +deleted file mode 100644 +index 0cb845a..0000000 +--- a/packages/app/cypress.json ++++ /dev/null +@@ -1,6 +0,0 @@ +-{ +- "baseUrl": "http://localhost:3001", +- "fixturesFolder": false, +- "pluginsFile": false, +- "retries": 3 +-} +diff --git a/packages/app/cypress/.eslintrc.json b/packages/app/cypress/.eslintrc.json +deleted file mode 100644 +index 2b3a458..0000000 +--- a/packages/app/cypress/.eslintrc.json ++++ /dev/null +@@ -1,21 +0,0 @@ +-{ +- "plugins": ["cypress"], +- "extends": ["plugin:cypress/recommended"], +- "rules": { +- "jest/expect-expect": [ +- "error", +- { +- "assertFunctionNames": ["expect", "cy.contains"] +- } +- ], +- "import/no-extraneous-dependencies": [ +- "error", +- { +- "devDependencies": true, +- "optionalDependencies": true, +- "peerDependencies": true, +- "bundledDependencies": true +- } +- ] +- } +-} +diff --git a/packages/app/cypress/integration/app.js b/packages/app/cypress/integration/app.js +deleted file mode 100644 +index 43fb2e3..0000000 +--- a/packages/app/cypress/integration/app.js ++++ /dev/null +@@ -1,6 +0,0 @@ +-describe('App', () => { +- it('should render the catalog', () => { +- cy.visit('/'); +- cy.contains('My Company Catalog'); +- }); +-}); +diff --git a/packages/app/e2e-tests/app.test.ts b/packages/app/e2e-tests/app.test.ts +new file mode 100644 +index 0000000..d45bc0d +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,23 @@ ++/* ++ * Copyright 2020 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { test, expect } from '@playwright/test'; ++ ++test('App should render the welcome page', async ({ page }) => { ++ await page.goto('/'); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index dd1b831..2100405 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -13,34 +13,30 @@ + "test": "backstage-cli package test", +- "lint": "backstage-cli package lint", +- "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", +- "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", +- "cy:dev": "cypress open", +- "cy:run": "cypress run --browser chrome" ++ "lint": "backstage-cli package lint" + }, + "dependencies": { +- "@backstage/app-defaults": "^1.0.7", +- "@backstage/catalog-model": "^1.1.2", +- "@backstage/cli": "^0.20.0", +- "@backstage/core-app-api": "^1.1.1", +- "@backstage/core-components": "^0.11.2", +- "@backstage/core-plugin-api": "^1.0.7", +- "@backstage/integration-react": "^1.1.5", +- "@backstage/plugin-api-docs": "^0.8.10", +- "@backstage/plugin-catalog": "^1.6.0", +- "@backstage/plugin-catalog-common": "^1.0.7", +- "@backstage/plugin-catalog-graph": "^0.2.22", +- "@backstage/plugin-catalog-import": "^0.9.0", +- "@backstage/plugin-catalog-react": "^1.2.0", +- "@backstage/plugin-github-actions": "^0.5.10", +- "@backstage/plugin-org": "^0.5.10", +- "@backstage/plugin-permission-react": "^0.4.6", +- "@backstage/plugin-scaffolder": "^1.7.0", +- "@backstage/plugin-search": "^1.0.3", +- "@backstage/plugin-search-react": "^1.2.0", +- "@backstage/plugin-tech-radar": "^0.5.17", +- "@backstage/plugin-techdocs": "^1.3.3", +- "@backstage/plugin-techdocs-react": "^1.0.5", +- "@backstage/plugin-techdocs-module-addons-contrib": "^1.0.5", +- "@backstage/plugin-user-settings": "^0.5.0", +- "@backstage/theme": "^0.2.16", ++ "@backstage/app-defaults": "^1.4.5-next.1", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/core-app-api": "^1.11.1-next.0", ++ "@backstage/core-components": "^0.13.8-next.1", ++ "@backstage/core-plugin-api": "^1.8.0-next.0", ++ "@backstage/integration-react": "^1.1.21-next.1", ++ "@backstage/plugin-api-docs": "^0.9.13-next.1", ++ "@backstage/plugin-catalog": "^1.15.0-next.1", ++ "@backstage/plugin-catalog-common": "^1.0.17", ++ "@backstage/plugin-catalog-graph": "^0.2.38-next.1", ++ "@backstage/plugin-catalog-import": "^0.10.2-next.1", ++ "@backstage/plugin-catalog-react": "^1.9.0-next.1", ++ "@backstage/plugin-github-actions": "^0.6.7-next.1", ++ "@backstage/plugin-org": "^0.6.16-next.1", ++ "@backstage/plugin-permission-react": "^0.4.17-next.0", ++ "@backstage/plugin-scaffolder": "^1.16.0-next.1", ++ "@backstage/plugin-search": "^1.4.2-next.1", ++ "@backstage/plugin-search-react": "^1.7.2-next.1", ++ "@backstage/plugin-tech-radar": "^0.6.10-next.1", ++ "@backstage/plugin-techdocs": "^1.8.1-next.1", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.2-next.1", ++ "@backstage/plugin-techdocs-react": "^1.1.13-next.1", ++ "@backstage/plugin-user-settings": "^0.7.12-next.1", ++ "@backstage/theme": "^0.4.4-next.0", + "@material-ui/core": "^4.12.2", +@@ -55,3 +51,4 @@ + "devDependencies": { +- "@backstage/test-utils": "^1.2.1", ++ "@backstage/test-utils": "^1.4.5-next.0", ++ "@playwright/test": "^1.32.3", + "@testing-library/jest-dom": "^5.10.1", +@@ -59,8 +56,5 @@ + "@testing-library/user-event": "^14.0.0", +- "@types/node": "^16.11.26", ++ "@testing-library/dom": "^8.0.0", + "@types/react-dom": "*", +- "cross-env": "^7.0.0", +- "cypress": "^9.7.0", +- "eslint-plugin-cypress": "^2.10.3", +- "start-server-and-test": "^1.10.11" ++ "cross-env": "^7.0.0" + }, +diff --git a/packages/app/public/index.html b/packages/app/public/index.html +index a936c73..fc758ee 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -10,3 +10,2 @@ + /> +- + +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 46cb786..8d62f29 100644 +--- a/packages/app/src/App.tsx ++++ b/packages/app/src/App.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { Navigate, Route } from 'react-router'; ++import { Navigate, Route } from 'react-router-dom'; + import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; +@@ -31,3 +31,3 @@ import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; + import { createApp } from '@backstage/app-defaults'; +-import { FlatRoutes } from '@backstage/core-app-api'; ++import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; + import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; +@@ -42,2 +42,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -48,2 +49,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -55,5 +57,2 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( +@@ -99,4 +98,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -106,5 +105,3 @@ const App = () => ( + +- ++ , + ); +- +-export default App; +diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx +index 5400421..6768b48 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,3 +1,3 @@ + import React, { PropsWithChildren } from 'react'; +-import { Link, makeStyles } from '@material-ui/core'; ++import { makeStyles } from '@material-ui/core'; + import HomeIcon from '@material-ui/icons/Home'; +@@ -9,3 +9,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -25,2 +24,3 @@ import { + useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -50,9 +50,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx +index 54a05ee..6722ea2 100644 +--- a/packages/app/src/components/catalog/EntityPage.tsx ++++ b/packages/app/src/components/catalog/EntityPage.tsx +@@ -27,2 +27,4 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +@@ -103,2 +105,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx +index 9f11d0c..1788dde 100644 +--- a/packages/app/src/components/search/SearchPage.tsx ++++ b/packages/app/src/components/search/SearchPage.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; ++import { makeStyles, Theme, Grid, Paper } from '@material-ui/core'; + +@@ -12,3 +12,2 @@ import { SearchType } from '@backstage/plugin-search'; + import { +- DefaultResultListItem, + SearchBar, +@@ -114,37 +113,4 @@ const SearchPage = () => { + +- {({ results }) => ( +- +- {results.map(({ type, document, highlight, rank }) => { +- switch (type) { +- case 'software-catalog': +- return ( +- +- ); +- case 'techdocs': +- return ( +- +- ); +- default: +- return ( +- +- ); +- } +- })} +- +- )} ++ } /> ++ } /> + +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index f0be3ac..18548e9 100644 +--- a/packages/backend/Dockerfile ++++ b/packages/backend/Dockerfile +@@ -7,3 +7,3 @@ + # yarn tsc +-# yarn build ++# yarn build:backend + # +@@ -11,3 +11,10 @@ + +-FROM node:16-bullseye-slim ++FROM node:18-bookworm-slim ++ ++# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends python3 g++ build-essential && \ ++ yarn config set python /usr/bin/python3 + +@@ -18,4 +25,3 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + apt-get update && \ +- apt-get install -y --no-install-recommends libsqlite3-dev python3 build-essential && \ +- yarn config set python /usr/bin/python3 ++ apt-get install -y --no-install-recommends libsqlite3-dev + +diff --git a/packages/backend/README.md b/packages/backend/README.md +index 02426ef..3607b0a 100644 +--- a/packages/backend/README.md ++++ b/packages/backend/README.md +@@ -18,4 +18,2 @@ To run the example backend, first go to the project root and run + yarn install +-yarn tsc +-yarn build + ``` +@@ -27,7 +25,2 @@ After that, go to the `packages/backend` directory and run + ```bash +-AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x \ +-AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x \ +-AUTH_OAUTH2_CLIENT_ID=x AUTH_OAUTH2_CLIENT_SECRET=x \ +-AUTH_OAUTH2_AUTH_URL=x AUTH_OAUTH2_TOKEN_URL=x \ +-LOG_LEVEL=debug \ + yarn start +@@ -35,4 +28,4 @@ yarn start + +-Substitute `x` for actual values, or leave them as dummy values just to try out +-the backend without using the auth or sentry features. ++If you want to override any configuration locally, for example adding any secrets, ++you can do so in `app-config.local.yaml`. + +@@ -45,3 +38,3 @@ locations to the backend. These are places where the backend can find some + entity descriptor data to consume and serve. For more information, see +-[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog). ++[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). + +@@ -65,2 +58,2 @@ and + - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +-- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) ++- [Backstage Documentation](https://backstage.io/docs) +diff --git a/packages/backend/package.json b/packages/backend/package.json +index 7154350..f224eaf 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -18,21 +18,21 @@ + "dependencies": { ++ "@backstage/backend-common": "^0.19.9-next.1", ++ "@backstage/backend-tasks": "^0.5.12-next.1", ++ "@backstage/catalog-client": "^1.4.5", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/config": "^1.1.1", ++ "@backstage/plugin-app-backend": "^0.3.55-next.1", ++ "@backstage/plugin-auth-backend": "^0.20.0-next.1", ++ "@backstage/plugin-auth-node": "^0.4.1-next.1", ++ "@backstage/plugin-catalog-backend": "^1.15.0-next.1", ++ "@backstage/plugin-permission-common": "^0.7.9", ++ "@backstage/plugin-permission-node": "^0.7.18-next.1", ++ "@backstage/plugin-proxy-backend": "^0.4.5-next.1", ++ "@backstage/plugin-scaffolder-backend": "^1.19.0-next.1", ++ "@backstage/plugin-search-backend": "^1.4.7-next.1", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.16-next.1", ++ "@backstage/plugin-search-backend-node": "^1.2.11-next.1", ++ "@backstage/plugin-techdocs-backend": "^1.9.0-next.1", + "app": "link:../app", +- "@backstage/backend-common": "^0.15.2", +- "@backstage/backend-tasks": "^0.3.6", +- "@backstage/catalog-model": "^1.1.2", +- "@backstage/catalog-client": "^1.1.1", +- "@backstage/config": "^1.0.3", +- "@backstage/plugin-app-backend": "^0.3.37", +- "@backstage/plugin-auth-backend": "^0.17.0", +- "@backstage/plugin-auth-node": "^0.2.6", +- "@backstage/plugin-catalog-backend": "^1.5.0", +- "@backstage/plugin-permission-common": "^0.7.0", +- "@backstage/plugin-permission-node": "^0.7.0", +- "@backstage/plugin-proxy-backend": "^0.2.31", +- "@backstage/plugin-scaffolder-backend": "^1.7.0", +- "@backstage/plugin-search-backend": "^1.1.0", +- "@backstage/plugin-search-backend-module-pg": "^0.4.1", +- "@backstage/plugin-search-backend-node": "^1.0.3", +- "@backstage/plugin-techdocs-backend": "^1.4.0", +- "better-sqlite3": "^7.5.0", ++ "better-sqlite3": "^8.0.0", + "dockerode": "^3.3.1", +@@ -44,6 +44,6 @@ + "devDependencies": { +- "@backstage/cli": "^0.20.0", ++ "@backstage/cli": "^0.24.0-next.1", + "@types/dockerode": "^3.3.0", +- "@types/express-serve-static-core": "^4.17.5", + "@types/express": "^4.17.6", ++ "@types/express-serve-static-core": "^4.17.5", + "@types/luxon": "^2.0.4" +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index c4736a5..04c4ff9 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -17,3 +17,3 @@ import { + DatabaseManager, +- SingleHostDiscovery, ++ HostDiscovery, + UrlReaders, +@@ -37,3 +37,3 @@ function makeCreateEnv(config: Config) { + const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++ const discovery = HostDiscovery.fromConfig(config); + const cacheManager = CacheManager.fromConfig(config); +@@ -41,3 +41,3 @@ function makeCreateEnv(config: Config) { + const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); ++ const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); + +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +index ef46f07..a12fee2 100644 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ b/packages/backend/src/plugins/scaffolder.ts +@@ -19,2 +19,3 @@ export default async function createPlugin( + identity: env.identity, ++ permissions: env.permissions, + }); +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..37c7fb1 +--- /dev/null ++++ b/playwright.config.ts +@@ -0,0 +1,60 @@ ++/* ++ * Copyright 2023 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { defineConfig } from '@playwright/test'; ++import { generateProjects } from '@backstage/e2e-test-utils/playwright'; ++ ++/** ++ * See https://playwright.dev/docs/test-configuration. ++ */ ++export default defineConfig({ ++ timeout: 60_000, ++ ++ expect: { ++ timeout: 5_000, ++ }, ++ ++ // Run your local dev server before starting the tests ++ webServer: process.env.CI ++ ? [] ++ : [ ++ { ++ command: 'yarn start', ++ port: 3000, ++ reuseExistingServer: true, ++ timeout: 60_000, ++ }, ++ ], ++ ++ forbidOnly: !!process.env.CI, ++ ++ retries: process.env.CI ? 2 : 0, ++ ++ reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]], ++ ++ use: { ++ actionTimeout: 0, ++ baseURL: ++ process.env.PLAYWRIGHT_URL ?? ++ (process.env.CI ? 'http://localhost:7007' : 'http://localhost:3000'), ++ screenshot: 'only-on-failure', ++ trace: 'on-first-retry', ++ }, ++ ++ outputDir: 'node_modules/.cache/e2e-test-results', ++ ++ projects: generateProjects(), // Find all packages with e2e-test folders ++}); +diff --git a/plugins/README.md b/plugins/README.md +index 58dc32c..d7865fd 100644 +--- a/plugins/README.md ++++ b/plugins/README.md +@@ -6,3 +6,3 @@ separate folder of its own. + If you want to create a new plugin here, go to your project root directory, run +-the command `yarn backstage-cli create`, and follow the on-screen instructions. ++the command `yarn new`, and follow the on-screen instructions. + diff --git a/diffs/0.4.34..0.5.7-next.1.diff b/diffs/0.4.34..0.5.7-next.1.diff new file mode 100644 index 00000000..483fe78e --- /dev/null +++ b/diffs/0.4.34..0.5.7-next.1.diff @@ -0,0 +1,605 @@ +diff --git a/.eslintignore b/.eslintignore +new file mode 100644 +index 0000000..e5b1994 +--- /dev/null ++++ b/.eslintignore +@@ -0,0 +1 @@ ++playwright.config.ts +diff --git a/.gitignore b/.gitignore +index d452ac2..fbf8139 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -51 +51,4 @@ site + *.session.sql ++ ++# E2E test reports ++e2e-test-report/ +diff --git a/app-config.production.yaml b/app-config.production.yaml +index df09dac..8f0751c 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -32,3 +32,3 @@ catalog: + # Overrides the default list locations from app-config.yaml as these contain example data. +- # See https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog for more details ++ # See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details + # on how to get entities into the catalog. +diff --git a/app-config.yaml b/app-config.yaml +index 1a45d40..08d2abf 100644 +--- a/app-config.yaml ++++ b/app-config.yaml +@@ -9,3 +9,3 @@ backend: + # Used for enabling authentication, secret is shared by all backend plugins +- # See https://backstage.io/docs/tutorials/backend-to-backend-auth for ++ # See https://backstage.io/docs/auth/service-to-service-auth for + # information on the format +@@ -32,4 +32,2 @@ backend: + connection: ':memory:' +- cache: +- store: memory + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir +@@ -48,5 +46,8 @@ integrations: + proxy: +- '/test': +- target: 'https://example.com' +- changeOrigin: true ++ ### Example for how to add a proxy endpoint for the frontend. ++ ### A typical reason to do this is to handle HTTPS and CORS for internal services. ++ # endpoints: ++ # '/test': ++ # target: 'https://example.com' ++ # changeOrigin: true + +diff --git a/backstage.json b/backstage.json +index 78259a4..707af29 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "1.8.0" ++ "version": "1.20.0-next.1" + } +diff --git a/examples/template/template.yaml b/examples/template/template.yaml +index 50052b7..33f262b 100644 +--- a/examples/template/template.yaml ++++ b/examples/template/template.yaml +@@ -63,3 +63,3 @@ spec: + input: +- repoContentsUrl: ${{ steps.publish.output.repoContentsUrl }} ++ repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} + catalogInfoPath: '/catalog-info.yaml' +@@ -70,5 +70,5 @@ spec: + - title: Repository +- url: ${{ steps.publish.output.remoteUrl }} ++ url: ${{ steps['publish'].output.remoteUrl }} + - title: Open in catalog + icon: catalog +- entityRef: ${{ steps.register.output.entityRef }} ++ entityRef: ${{ steps['register'].output.entityRef }} +diff --git a/lerna.json b/lerna.json +index 322929d..529a62f 100644 +--- a/lerna.json ++++ b/lerna.json +@@ -3,4 +3,4 @@ + "npmClient": "yarn", +- "useWorkspaces": true, +- "version": "0.1.0" ++ "version": "0.1.0", ++ "$schema": "node_modules/lerna/schemas/lerna-schema.json" + } +diff --git a/package.json b/package.json +index 4ff2d48..2364da8 100644 +--- a/package.json ++++ b/package.json +@@ -5,3 +5,3 @@ + "engines": { +- "node": "16 || 18" ++ "node": "18 || 20" + }, +@@ -19,2 +19,4 @@ + "test:all": "backstage-cli repo test --coverage", ++ "test:e2e": "playwright test", ++ "fix": "backstage-cli repo fix", + "lint": "backstage-cli repo lint --since origin/master", +@@ -22,3 +24,2 @@ + "prettier:check": "prettier --check .", +- "create-plugin": "backstage-cli create-plugin --scope internal", + "new": "backstage-cli new --scope internal" +@@ -32,9 +33,11 @@ + "devDependencies": { +- "@backstage/cli": "^0.21.0", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/e2e-test-utils": "^0.1.0", ++ "@playwright/test": "^1.32.3", + "@spotify/prettier-config": "^12.0.0", +- "concurrently": "^6.0.0", +- "lerna": "^4.0.0", ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^9.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.6.4", +- "node-gyp": "^9.0.0" ++ "typescript": "~5.2.0" + }, +diff --git a/packages/app/.eslintignore b/packages/app/.eslintignore +new file mode 100644 +index 0000000..a48cf0d +--- /dev/null ++++ b/packages/app/.eslintignore +@@ -0,0 +1 @@ ++public +diff --git a/packages/app/cypress.json b/packages/app/cypress.json +deleted file mode 100644 +index 0cb845a..0000000 +--- a/packages/app/cypress.json ++++ /dev/null +@@ -1,6 +0,0 @@ +-{ +- "baseUrl": "http://localhost:3001", +- "fixturesFolder": false, +- "pluginsFile": false, +- "retries": 3 +-} +diff --git a/packages/app/cypress/.eslintrc.json b/packages/app/cypress/.eslintrc.json +deleted file mode 100644 +index 2b3a458..0000000 +--- a/packages/app/cypress/.eslintrc.json ++++ /dev/null +@@ -1,21 +0,0 @@ +-{ +- "plugins": ["cypress"], +- "extends": ["plugin:cypress/recommended"], +- "rules": { +- "jest/expect-expect": [ +- "error", +- { +- "assertFunctionNames": ["expect", "cy.contains"] +- } +- ], +- "import/no-extraneous-dependencies": [ +- "error", +- { +- "devDependencies": true, +- "optionalDependencies": true, +- "peerDependencies": true, +- "bundledDependencies": true +- } +- ] +- } +-} +diff --git a/packages/app/cypress/integration/app.js b/packages/app/cypress/integration/app.js +deleted file mode 100644 +index 43fb2e3..0000000 +--- a/packages/app/cypress/integration/app.js ++++ /dev/null +@@ -1,6 +0,0 @@ +-describe('App', () => { +- it('should render the catalog', () => { +- cy.visit('/'); +- cy.contains('My Company Catalog'); +- }); +-}); +diff --git a/packages/app/e2e-tests/app.test.ts b/packages/app/e2e-tests/app.test.ts +new file mode 100644 +index 0000000..d45bc0d +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,23 @@ ++/* ++ * Copyright 2020 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { test, expect } from '@playwright/test'; ++ ++test('App should render the welcome page', async ({ page }) => { ++ await page.goto('/'); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index a085f61..2100405 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -13,34 +13,30 @@ + "test": "backstage-cli package test", +- "lint": "backstage-cli package lint", +- "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", +- "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", +- "cy:dev": "cypress open", +- "cy:run": "cypress run --browser chrome" ++ "lint": "backstage-cli package lint" + }, + "dependencies": { +- "@backstage/app-defaults": "^1.0.8", +- "@backstage/catalog-model": "^1.1.3", +- "@backstage/cli": "^0.21.0", +- "@backstage/core-app-api": "^1.2.0", +- "@backstage/core-components": "^0.12.0", +- "@backstage/core-plugin-api": "^1.1.0", +- "@backstage/integration-react": "^1.1.6", +- "@backstage/plugin-api-docs": "^0.8.11", +- "@backstage/plugin-catalog": "^1.6.1", +- "@backstage/plugin-catalog-common": "^1.0.8", +- "@backstage/plugin-catalog-graph": "^0.2.23", +- "@backstage/plugin-catalog-import": "^0.9.1", +- "@backstage/plugin-catalog-react": "^1.2.1", +- "@backstage/plugin-github-actions": "^0.5.11", +- "@backstage/plugin-org": "^0.6.0", +- "@backstage/plugin-permission-react": "^0.4.7", +- "@backstage/plugin-scaffolder": "^1.8.0", +- "@backstage/plugin-search": "^1.0.4", +- "@backstage/plugin-search-react": "^1.2.1", +- "@backstage/plugin-tech-radar": "^0.5.18", +- "@backstage/plugin-techdocs": "^1.4.0", +- "@backstage/plugin-techdocs-react": "^1.0.6", +- "@backstage/plugin-techdocs-module-addons-contrib": "^1.0.6", +- "@backstage/plugin-user-settings": "^0.5.1", +- "@backstage/theme": "^0.2.16", ++ "@backstage/app-defaults": "^1.4.5-next.1", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/core-app-api": "^1.11.1-next.0", ++ "@backstage/core-components": "^0.13.8-next.1", ++ "@backstage/core-plugin-api": "^1.8.0-next.0", ++ "@backstage/integration-react": "^1.1.21-next.1", ++ "@backstage/plugin-api-docs": "^0.9.13-next.1", ++ "@backstage/plugin-catalog": "^1.15.0-next.1", ++ "@backstage/plugin-catalog-common": "^1.0.17", ++ "@backstage/plugin-catalog-graph": "^0.2.38-next.1", ++ "@backstage/plugin-catalog-import": "^0.10.2-next.1", ++ "@backstage/plugin-catalog-react": "^1.9.0-next.1", ++ "@backstage/plugin-github-actions": "^0.6.7-next.1", ++ "@backstage/plugin-org": "^0.6.16-next.1", ++ "@backstage/plugin-permission-react": "^0.4.17-next.0", ++ "@backstage/plugin-scaffolder": "^1.16.0-next.1", ++ "@backstage/plugin-search": "^1.4.2-next.1", ++ "@backstage/plugin-search-react": "^1.7.2-next.1", ++ "@backstage/plugin-tech-radar": "^0.6.10-next.1", ++ "@backstage/plugin-techdocs": "^1.8.1-next.1", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.2-next.1", ++ "@backstage/plugin-techdocs-react": "^1.1.13-next.1", ++ "@backstage/plugin-user-settings": "^0.7.12-next.1", ++ "@backstage/theme": "^0.4.4-next.0", + "@material-ui/core": "^4.12.2", +@@ -55,3 +51,4 @@ + "devDependencies": { +- "@backstage/test-utils": "^1.2.2", ++ "@backstage/test-utils": "^1.4.5-next.0", ++ "@playwright/test": "^1.32.3", + "@testing-library/jest-dom": "^5.10.1", +@@ -59,8 +56,5 @@ + "@testing-library/user-event": "^14.0.0", +- "@types/node": "^16.11.26", ++ "@testing-library/dom": "^8.0.0", + "@types/react-dom": "*", +- "cross-env": "^7.0.0", +- "cypress": "^9.7.0", +- "eslint-plugin-cypress": "^2.10.3", +- "start-server-and-test": "^1.10.11" ++ "cross-env": "^7.0.0" + }, +diff --git a/packages/app/public/index.html b/packages/app/public/index.html +index c6083b3..fc758ee 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -10,3 +10,2 @@ + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index 82bc479..b94cac7 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -12,5 +12,5 @@ describe('App', () => { + app: { title: 'Test' }, +- backend: { baseUrl: 'http://localhost:7000' }, ++ backend: { baseUrl: 'http://localhost:7007' }, + techdocs: { +- storageUrl: 'http://localhost:7000/api/techdocs/static/docs', ++ storageUrl: 'http://localhost:7007/api/techdocs/static/docs', + }, +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 8a53583..8d62f29 100644 +--- a/packages/app/src/App.tsx ++++ b/packages/app/src/App.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { Navigate, Route } from 'react-router'; ++import { Navigate, Route } from 'react-router-dom'; + import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; +@@ -13,2 +13,3 @@ import { + import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; ++import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +@@ -16,3 +17,2 @@ import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -21,2 +21,4 @@ import { + } from '@backstage/plugin-techdocs'; ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; + import { UserSettingsPage } from '@backstage/plugin-user-settings'; +@@ -29,3 +31,6 @@ import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; + import { createApp } from '@backstage/app-defaults'; +-import { FlatRoutes } from '@backstage/core-app-api'; ++import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; ++import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; ++import { RequirePermission } from '@backstage/plugin-permission-react'; ++import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; + +@@ -37,5 +42,6 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); + bind(apiDocsPlugin.externalRoutes, { +- createComponent: scaffolderPlugin.routes.root, ++ registerApi: catalogImportPlugin.routes.importPage, + }); +@@ -43,2 +49,6 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, ++ }); ++ bind(orgPlugin.externalRoutes, { ++ catalogIndex: catalogPlugin.routes.catalogIndex, + }); +@@ -47,8 +57,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -60,5 +67,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -73,3 +82,10 @@ const routes = ( + /> +- } /> ++ ++ ++ ++ } ++ /> + }> +@@ -78,2 +94,3 @@ const routes = ( + } /> ++ } /> + +@@ -81,4 +98,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -88,5 +105,3 @@ const App = () => ( + +- ++ , + ); +- +-export default App; +diff --git a/packages/app/src/components/Root/LogoFull.tsx b/packages/app/src/components/Root/LogoFull.tsx +index c7b1c84..47e3b73 100644 +--- a/packages/app/src/components/Root/LogoFull.tsx ++++ b/packages/app/src/components/Root/LogoFull.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/LogoIcon.tsx b/packages/app/src/components/Root/LogoIcon.tsx +index 073cf6e..7eae8c7 100644 +--- a/packages/app/src/components/Root/LogoIcon.tsx ++++ b/packages/app/src/components/Root/LogoIcon.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx +index ec59b0b..6768b48 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,19 +1,3 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- +-import React, { useContext, PropsWithChildren } from 'react'; +-import { Link, makeStyles } from '@material-ui/core'; ++import React, { PropsWithChildren } from 'react'; ++import { makeStyles } from '@material-ui/core'; + import HomeIcon from '@material-ui/icons/Home'; +@@ -25,4 +9,6 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; +-import { Settings as SidebarSettings } from '@backstage/plugin-user-settings'; ++import { ++ Settings as SidebarSettings, ++ UserSettingsSignInAvatar, ++} from '@backstage/plugin-user-settings'; + import { SidebarSearchModal } from '@backstage/plugin-search'; +@@ -30,10 +16,14 @@ import { + Sidebar, +- SidebarPage, + sidebarConfig, +- SidebarContext, +- SidebarItem, + SidebarDivider, +- SidebarSpace, ++ SidebarGroup, ++ SidebarItem, ++ SidebarPage, + SidebarScrollWrapper, ++ SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; ++import MenuIcon from '@material-ui/icons/Menu'; ++import SearchIcon from '@material-ui/icons/Search'; + +@@ -56,3 +46,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -60,8 +50,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -76,17 +61,27 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ } to="/search"> ++ ++ + +- {/* Global nav, not org-specific */} +- +- +- +- +- {/* End global nav */} +- +- +- +- ++ }> ++ {/* Global nav, not org-specific */} ++ ++ ++ ++ ++ {/* End global nav */} ++ ++ ++ ++ ++ + + +- ++ } ++ to="/settings" ++ > ++ ++ + +diff --git a/packages/app/src/components/Root/index.ts b/packages/app/src/components/Root/index.ts +index dff706f..3528881 100644 +--- a/packages/app/src/components/Root/index.ts ++++ b/packages/app/src/components/Root/index.ts +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + export { Root } from './Root'; +diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx +index d3b4b78..6722ea2 100644 +--- a/packages/app/src/components/catalog/EntityPage.tsx ++++ b/packages/app/src/components/catalog/EntityPage.tsx +@@ -1,16 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ + import React from 'react'; +@@ -29,3 +14,2 @@ import { + EntityDependsOnResourcesCard, +- EntitySystemDiagramCard, + EntityHasComponentsCard, +@@ -43,2 +27,4 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +@@ -56,2 +42,27 @@ import { EntityTechdocsContent } from '@backstage/plugin-techdocs'; + import { EmptyState } from '@backstage/core-components'; ++import { ++ Direction, ++ EntityCatalogGraphCard, ++} from '@backstage/plugin-catalog-graph'; ++import { ++ RELATION_API_CONSUMED_BY, ++ RELATION_API_PROVIDED_BY, ++ RELATION_CONSUMES_API, ++ RELATION_DEPENDENCY_OF, ++ RELATION_DEPENDS_ON, ++ RELATION_HAS_PART, ++ RELATION_PART_OF, ++ RELATION_PROVIDES_API, ++} from '@backstage/catalog-model'; ++ ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); + +@@ -94,2 +105,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -110,2 +129,6 @@ const overviewContent = ( + ++ ++ ++ ++ + +@@ -152,3 +175,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -179,3 +202,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -198,3 +221,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -225,2 +248,5 @@ const apiPage = ( + ++ ++ ++ + +@@ -292,3 +318,9 @@ const systemPage = ( + +- ++ ++ ++ ++ ++ ++ ++ + +@@ -304,3 +336,19 @@ const systemPage = ( + +- ++ + +@@ -317,2 +365,5 @@ const domainPage = ( + ++ ++ ++ + +diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx +index 7b3c2b2..1788dde 100644 +--- a/packages/app/src/components/search/SearchPage.tsx ++++ b/packages/app/src/components/search/SearchPage.tsx +@@ -1,5 +1,12 @@ + import React from 'react'; +-import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; ++import { makeStyles, Theme, Grid, Paper } from '@material-ui/core'; + +-import { CatalogResultListItem } from '@backstage/plugin-catalog'; ++import { CatalogSearchResultListItem } from '@backstage/plugin-catalog'; ++import { ++ catalogApiRef, ++ CATALOG_FILTER_EXISTS, ++} from '@backstage/plugin-catalog-react'; ++import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; ++ ++import { SearchType } from '@backstage/plugin-search'; + import { +@@ -8,5 +15,13 @@ import { + SearchResult, +- DefaultResultListItem, +-} from '@backstage/plugin-search'; +-import { Content, Header, Page } from '@backstage/core-components'; ++ SearchPagination, ++ useSearch, ++} from '@backstage/plugin-search-react'; ++import { ++ CatalogIcon, ++ Content, ++ DocsIcon, ++ Header, ++ Page, ++} from '@backstage/core-components'; ++import { useApi } from '@backstage/core-plugin-api'; + +@@ -18,2 +33,3 @@ const useStyles = makeStyles((theme: Theme) => ({ + padding: theme.spacing(2), ++ marginTop: theme.spacing(2), + }, +@@ -28,2 +44,4 @@ const SearchPage = () => { + const classes = useStyles(); ++ const { types } = useSearch(); ++ const catalogApi = useApi(catalogApiRef); + +@@ -36,3 +54,3 @@ const SearchPage = () => { + +- ++ + +@@ -40,5 +58,43 @@ const SearchPage = () => { + ++ , ++ }, ++ { ++ value: 'techdocs', ++ name: 'Documentation', ++ icon: , ++ }, ++ ]} ++ /> + ++ {types.includes('techdocs') && ( ++ { ++ // Return a list of entities which are documented. ++ const { items } = await catalogApi.getEntities({ ++ fields: ['metadata.name'], ++ filter: { ++ 'metadata.annotations.backstage.io/techdocs-ref': ++ CATALOG_FILTER_EXISTS, ++ }, ++ }); ++ ++ const names = items.map(entity => entity.metadata.name); ++ names.sort(); ++ return names; ++ }} ++ /> ++ )} + { + className={classes.filter} ++ label="Lifecycle" + name="lifecycle" +@@ -54,25 +111,6 @@ const SearchPage = () => { + ++ + +- {({ results }) => ( +- +- {results.map(({ type, document }) => { +- switch (type) { +- case 'software-catalog': +- return ( +- +- ); +- default: +- return ( +- +- ); +- } +- })} +- +- )} ++ } /> ++ } /> + +diff --git a/packages/backend/.eslintrc.js b/packages/backend/.eslintrc.js +index 16a033d..e2a53a6 100644 +--- a/packages/backend/.eslintrc.js ++++ b/packages/backend/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint.backend')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index 31231a3..18548e9 100644 +--- a/packages/backend/Dockerfile ++++ b/packages/backend/Dockerfile +@@ -7,3 +7,3 @@ + # yarn tsc +-# yarn build ++# yarn build:backend + # +@@ -11,6 +11,29 @@ + +-FROM node:14-buster-slim ++FROM node:18-bookworm-slim + ++# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends python3 g++ build-essential && \ ++ yarn config set python /usr/bin/python3 ++ ++# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, ++# in which case you should also move better-sqlite3 to "devDependencies" in package.json. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends libsqlite3-dev ++ ++# From here on we use the least-privileged `node` user to run the backend. ++USER node ++ ++# This should create the app dir as `node`. ++# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. ++# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. + WORKDIR /app + ++# This switches many Node.js dependencies to production mode. ++ENV NODE_ENV production ++ + # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. +@@ -18,11 +41,12 @@ WORKDIR /app + # and along with yarn.lock and the root package.json, that's enough to run yarn install. +-COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ ++COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ + RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz + +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + + # Then copy the rest of the backend bundle, along with any other files we might want. +-COPY packages/backend/dist/bundle.tar.gz app-config.yaml ./ ++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"] +diff --git a/packages/backend/README.md b/packages/backend/README.md +index 81e0f80..3607b0a 100644 +--- a/packages/backend/README.md ++++ b/packages/backend/README.md +@@ -18,4 +18,2 @@ To run the example backend, first go to the project root and run + yarn install +-yarn tsc +-yarn build + ``` +@@ -27,7 +25,2 @@ After that, go to the `packages/backend` directory and run + ```bash +-AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x \ +-AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x \ +-AUTH_OAUTH2_CLIENT_ID=x AUTH_OAUTH2_CLIENT_SECRET=x \ +-AUTH_OAUTH2_AUTH_URL=x AUTH_OAUTH2_TOKEN_URL=x \ +-LOG_LEVEL=debug \ + yarn start +@@ -35,6 +28,6 @@ yarn start + +-Substitute `x` for actual values, or leave them as dummy values just to try out +-the backend without using the auth or sentry features. ++If you want to override any configuration locally, for example adding any secrets, ++you can do so in `app-config.local.yaml`. + +-The backend starts up on port 7000 per default. ++The backend starts up on port 7007 per default. + +@@ -45,3 +38,3 @@ locations to the backend. These are places where the backend can find some + entity descriptor data to consume and serve. For more information, see +-[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog). ++[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). + +@@ -65,2 +58,2 @@ and + - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +-- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) ++- [Backstage Documentation](https://backstage.io/docs) +diff --git a/packages/backend/package.json b/packages/backend/package.json +index b73e678..f224eaf 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,27 +6,33 @@ + "private": true, ++ "backstage": { ++ "role": "backend" ++ }, + "scripts": { +- "build": "backstage-cli backend:bundle", +- "build-image": "docker build ../.. -f Dockerfile --tag backstage", +- "start": "backstage-cli backend:dev", +- "lint": "backstage-cli lint", +- "test": "backstage-cli test", +- "clean": "backstage-cli clean", +- "migrate:create": "knex migrate:make -x ts" ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "lint": "backstage-cli package lint", ++ "test": "backstage-cli package test", ++ "clean": "backstage-cli package clean", ++ "build-image": "docker build ../.. -f Dockerfile --tag backstage" + }, + "dependencies": { +- "app": "0.0.0", +- "@backstage/backend-common": "^0.9.10", +- "@backstage/catalog-model": "^0.9.7", +- "@backstage/catalog-client": "^0.5.2", +- "@backstage/config": "^0.1.11", +- "@backstage/plugin-app-backend": "^0.3.19", +- "@backstage/plugin-auth-backend": "^0.4.8", +- "@backstage/plugin-catalog-backend": "^0.17.4", +- "@backstage/plugin-proxy-backend": "^0.2.13", +- "@backstage/plugin-scaffolder-backend": "^0.15.13", +- "@backstage/plugin-search-backend": "^0.2.6", +- "@backstage/plugin-search-backend-node": "^0.4.3", +- "@backstage/plugin-techdocs-backend": "^0.10.8", +- "@gitbeaker/node": "^30.2.0", +- "@octokit/rest": "^18.5.3", ++ "@backstage/backend-common": "^0.19.9-next.1", ++ "@backstage/backend-tasks": "^0.5.12-next.1", ++ "@backstage/catalog-client": "^1.4.5", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/config": "^1.1.1", ++ "@backstage/plugin-app-backend": "^0.3.55-next.1", ++ "@backstage/plugin-auth-backend": "^0.20.0-next.1", ++ "@backstage/plugin-auth-node": "^0.4.1-next.1", ++ "@backstage/plugin-catalog-backend": "^1.15.0-next.1", ++ "@backstage/plugin-permission-common": "^0.7.9", ++ "@backstage/plugin-permission-node": "^0.7.18-next.1", ++ "@backstage/plugin-proxy-backend": "^0.4.5-next.1", ++ "@backstage/plugin-scaffolder-backend": "^1.19.0-next.1", ++ "@backstage/plugin-search-backend": "^1.4.7-next.1", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.16-next.1", ++ "@backstage/plugin-search-backend-node": "^1.2.11-next.1", ++ "@backstage/plugin-techdocs-backend": "^1.9.0-next.1", ++ "app": "link:../app", ++ "better-sqlite3": "^8.0.0", + "dockerode": "^3.3.1", +@@ -34,4 +40,3 @@ + "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "pg": "^8.3.0", + "winston": "^3.2.1" +@@ -39,6 +44,7 @@ + "devDependencies": { +- "@backstage/cli": "^0.9.0", ++ "@backstage/cli": "^0.24.0-next.1", + "@types/dockerode": "^3.3.0", + "@types/express": "^4.17.6", +- "@types/express-serve-static-core": "^4.17.5" ++ "@types/express-serve-static-core": "^4.17.5", ++ "@types/luxon": "^2.0.4" + }, +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index f2b14b2..04c4ff9 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -17,5 +17,7 @@ import { + DatabaseManager, +- SingleHostDiscovery, ++ HostDiscovery, + UrlReaders, ++ ServerTokenManager, + } from '@backstage/backend-common'; ++import { TaskScheduler } from '@backstage/backend-tasks'; + import { Config } from '@backstage/config'; +@@ -29,2 +31,4 @@ import search from './plugins/search'; + import { PluginEnvironment } from './types'; ++import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; + +@@ -33,8 +37,17 @@ function makeCreateEnv(config: Config) { + const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++ const discovery = HostDiscovery.fromConfig(config); ++ const cacheManager = CacheManager.fromConfig(config); ++ const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); ++ const tokenManager = ServerTokenManager.noop(); ++ const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); + +- root.info(`Created UrlReader ${reader}`); ++ const identity = DefaultIdentityClient.create({ ++ discovery, ++ }); ++ const permissions = ServerPermissionClient.fromConfig(config, { ++ discovery, ++ tokenManager, ++ }); + +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++ root.info(`Created UrlReader ${reader}`); + +@@ -44,3 +57,15 @@ function makeCreateEnv(config: Config) { + const cache = cacheManager.forPlugin(plugin); +- return { logger, database, cache, config, reader, discovery }; ++ const scheduler = taskScheduler.forPlugin(plugin); ++ return { ++ logger, ++ database, ++ cache, ++ config, ++ reader, ++ discovery, ++ tokenManager, ++ scheduler, ++ permissions, ++ identity, ++ }; + }; +@@ -70,2 +95,4 @@ async function main() { + apiRouter.use('/search', await search(searchEnv)); ++ ++ // Add backends ABOVE this line; this 404 handler is the catch-all fallback + apiRouter.use(notFoundHandler()); +@@ -85,3 +112,3 @@ module.hot?.accept(); + main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); ++ console.error('Backend failed to start up', error); + process.exit(1); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +index 07fb04f..7c37f68 100644 +--- a/packages/backend/src/plugins/app.ts ++++ b/packages/backend/src/plugins/app.ts +@@ -4,9 +4,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + return await createRouter({ +- logger, +- config, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, + appPackageName: 'app', +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +index 5216510..77eb6aa 100644 +--- a/packages/backend/src/plugins/auth.ts ++++ b/packages/backend/src/plugins/auth.ts +@@ -1,2 +1,6 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; ++import { ++ createRouter, ++ providers, ++ defaultAuthProviderFactories, ++} from '@backstage/plugin-auth-backend'; + import { Router } from 'express'; +@@ -4,9 +8,47 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, database, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, ++ providerFactories: { ++ ...defaultAuthProviderFactories, ++ ++ // This replaces the default GitHub auth provider with a customized one. ++ // The `signIn` option enables sign-in for this provider, using the ++ // identity resolution logic that's provided in the `resolver` callback. ++ // ++ // This particular resolver makes all users share a single "guest" identity. ++ // It should only be used for testing and trying out Backstage. ++ // ++ // If you want to use a production ready resolver you can switch to ++ // the one that is commented out below, it looks up a user entity in the ++ // catalog using the GitHub username of the authenticated user. ++ // That resolver requires you to have user entities populated in the catalog, ++ // for example using https://backstage.io/docs/integrations/github/org ++ // ++ // There are other resolvers to choose from, and you can also create ++ // your own, see the auth documentation for more details: ++ // ++ // https://backstage.io/docs/auth/identity-resolver ++ github: providers.github.create({ ++ signIn: { ++ resolver(_, ctx) { ++ const userRef = 'user:default/guest'; // Must be a full entity reference ++ return ctx.issueToken({ ++ claims: { ++ sub: userRef, // The user's own identity ++ ent: [userRef], // A list of identities that the user claims ownership through ++ }, ++ }); ++ }, ++ // resolver: providers.github.resolvers.usernameMatchingUserEntityName(), ++ }, ++ }), ++ }, ++ }); + } +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +index 506f6d9..54ec393 100644 +--- a/packages/backend/src/plugins/proxy.ts ++++ b/packages/backend/src/plugins/proxy.ts +@@ -4,8 +4,10 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ }); + } +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +index 6be2e97..a12fee2 100644 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ b/packages/backend/src/plugins/scaffolder.ts +@@ -1,5 +1,3 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; + import { CatalogClient } from '@backstage/catalog-client'; + import { createRouter } from '@backstage/plugin-scaffolder-backend'; +-import Docker from 'dockerode'; + import { Router } from 'express'; +@@ -7,20 +5,17 @@ import type { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- database, +- reader, +- discovery, +-}: PluginEnvironment): Promise { +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- const catalogClient = new CatalogClient({ discoveryApi: discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ const catalogClient = new CatalogClient({ ++ discoveryApi: env.discovery, ++ }); + + return await createRouter({ +- containerRunner, +- logger, +- config, +- database, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ reader: env.reader, + catalogClient, +- reader, ++ identity: env.identity, ++ permissions: env.permissions, + }); +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +index 7fc317d..e9469dc 100644 +--- a/packages/backend/src/plugins/search.ts ++++ b/packages/backend/src/plugins/search.ts +@@ -7,18 +7,44 @@ import { + import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; ++import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend'; ++import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; ++import { Router } from 'express'; + +-export default async function createPlugin({ +- logger, +- discovery, +- config, +-}: PluginEnvironment) { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); ++ const searchEngine = new LunrSearchEngine({ ++ logger: env.logger, ++ }); ++ const indexBuilder = new IndexBuilder({ ++ logger: env.logger, ++ searchEngine, ++ }); ++ ++ const schedule = env.scheduler.createScheduledTaskRunner({ ++ frequency: { minutes: 10 }, ++ timeout: { minutes: 15 }, ++ // A 3 second delay gives the backend server a chance to initialize before ++ // any collators are executed, which may attempt requests against the API. ++ initialDelay: { seconds: 3 }, ++ }); + + // Collators are responsible for gathering documents known to plugins. This +- // particular collator gathers entities from the software catalog. ++ // collator gathers entities from the software catalog. ++ indexBuilder.addCollator({ ++ schedule, ++ factory: DefaultCatalogCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, ++ }), ++ }); ++ ++ // collator gathers entities from techdocs. + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { discovery }), ++ schedule, ++ factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ logger: env.logger, ++ tokenManager: env.tokenManager, ++ }), + }); +@@ -28,6 +54,4 @@ export default async function createPlugin({ + const { scheduler } = await indexBuilder.build(); ++ scheduler.start(); + +- // A 3 second delay gives the backend server a chance to initialize before +- // any collators are executed, which may attempt requests against the API. +- setTimeout(() => scheduler.start(), 3000); + useHotCleanup(module, () => scheduler.stop()); +@@ -36,3 +60,6 @@ export default async function createPlugin({ + engine: indexBuilder.getSearchEngine(), +- logger, ++ types: indexBuilder.getDocumentTypes(), ++ permissions: env.permissions, ++ config: env.config, ++ logger: env.logger, + }); +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +index 906d86d..be8bb0c 100644 +--- a/packages/backend/src/plugins/techdocs.ts ++++ b/packages/backend/src/plugins/techdocs.ts +@@ -11,12 +11,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, ++ const preparers = await Preparers.fromConfig(env.config, { ++ logger: env.logger, ++ reader: env.reader, + }); +@@ -28,4 +25,4 @@ export default async function createPlugin({ + // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, ++ const generators = await Generators.fromConfig(env.config, { ++ logger: env.logger, + containerRunner, +@@ -36,5 +33,5 @@ export default async function createPlugin({ + // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, ++ const publisher = await Publisher.fromConfig(env.config, { ++ logger: env.logger, ++ discovery: env.discovery, + }); +@@ -48,5 +45,6 @@ export default async function createPlugin({ + publisher, +- logger, +- config, +- discovery, ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ cache: env.cache, + }); +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +index 6c78a2a..9cd2c74 100644 +--- a/packages/backend/src/types.ts ++++ b/packages/backend/src/types.ts +@@ -6,4 +6,8 @@ import { + PluginEndpointDiscovery, ++ TokenManager, + UrlReader, + } from '@backstage/backend-common'; ++import { PluginTaskScheduler } from '@backstage/backend-tasks'; ++import { PermissionEvaluator } from '@backstage/plugin-permission-common'; ++import { IdentityApi } from '@backstage/plugin-auth-node'; + +@@ -16,2 +20,6 @@ export type PluginEnvironment = { + discovery: PluginEndpointDiscovery; ++ tokenManager: TokenManager; ++ scheduler: PluginTaskScheduler; ++ permissions: PermissionEvaluator; ++ identity: IdentityApi; + }; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..37c7fb1 +--- /dev/null ++++ b/playwright.config.ts +@@ -0,0 +1,60 @@ ++/* ++ * Copyright 2023 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { defineConfig } from '@playwright/test'; ++import { generateProjects } from '@backstage/e2e-test-utils/playwright'; ++ ++/** ++ * See https://playwright.dev/docs/test-configuration. ++ */ ++export default defineConfig({ ++ timeout: 60_000, ++ ++ expect: { ++ timeout: 5_000, ++ }, ++ ++ // Run your local dev server before starting the tests ++ webServer: process.env.CI ++ ? [] ++ : [ ++ { ++ command: 'yarn start', ++ port: 3000, ++ reuseExistingServer: true, ++ timeout: 60_000, ++ }, ++ ], ++ ++ forbidOnly: !!process.env.CI, ++ ++ retries: process.env.CI ? 2 : 0, ++ ++ reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]], ++ ++ use: { ++ actionTimeout: 0, ++ baseURL: ++ process.env.PLAYWRIGHT_URL ?? ++ (process.env.CI ? 'http://localhost:7007' : 'http://localhost:3000'), ++ screenshot: 'only-on-failure', ++ trace: 'on-first-retry', ++ }, ++ ++ outputDir: 'node_modules/.cache/e2e-test-results', ++ ++ projects: generateProjects(), // Find all packages with e2e-test folders ++}); +diff --git a/plugins/README.md b/plugins/README.md +new file mode 100644 +index 0000000..d7865fd +--- /dev/null ++++ b/plugins/README.md +@@ -0,0 +1,9 @@ ++# The Plugins Folder ++ ++This is where your own plugins and their associated modules live, each in a ++separate folder of its own. ++ ++If you want to create a new plugin here, go to your project root directory, run ++the command `yarn new`, and follow the on-screen instructions. ++ ++You can also check out existing plugins on [the plugin marketplace](https://backstage.io/plugins)! +diff --git a/yarn.lock b/yarn.lock +new file mode 100644 +index 0000000..5ad7fe2 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1 @@ ++# intentionally left empty diff --git a/diffs/0.4.5..0.5.7-next.1.diff b/diffs/0.4.5..0.5.7-next.1.diff new file mode 100644 index 00000000..0c5f24b1 --- /dev/null +++ b/diffs/0.4.5..0.5.7-next.1.diff @@ -0,0 +1,1911 @@ +diff --git a/.dockerignore b/.dockerignore +index 63c9c34..05edb62 100644 +--- a/.dockerignore ++++ b/.dockerignore +@@ -1,5 +1,8 @@ + .git ++.yarn/cache ++.yarn/install-state.gz + node_modules +-packages +-!packages/backend/dist ++packages/*/src ++packages/*/node_modules + plugins ++*.local.yaml +diff --git a/.eslintignore b/.eslintignore +new file mode 100644 +index 0000000..e5b1994 +--- /dev/null ++++ b/.eslintignore +@@ -0,0 +1 @@ ++playwright.config.ts +diff --git a/.gitignore b/.gitignore +index d16a8d3..fbf8139 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -17,2 +17,11 @@ node_modules/ + ++# Yarn 3 files ++.pnp.* ++.yarn/* ++!.yarn/patches ++!.yarn/plugins ++!.yarn/releases ++!.yarn/sdks ++!.yarn/versions ++ + # Node version directives +@@ -39 +48,7 @@ site + *-credentials.yaml ++ ++# vscode database functionality support files ++*.session.sql ++ ++# E2E test reports ++e2e-test-report/ +diff --git a/app-config.production.yaml b/app-config.production.yaml +index 5e36c23..8f0751c 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -1,3 +1,3 @@ + app: +- # Should be the same as backend.baseUrl when using the `app-backend` plugin ++ # Should be the same as backend.baseUrl when using the `app-backend` plugin. + baseUrl: http://localhost:7007 +@@ -5,4 +5,31 @@ app: + backend: ++ # Note that the baseUrl should be the URL that the browser and other clients ++ # should use when communicating with the backend, i.e. it needs to be ++ # reachable not just from within the backend host, but from all of your ++ # callers. When its value is "http://localhost:7007", it's strictly private ++ # and can't be reached by others. + baseUrl: http://localhost:7007 +- listen: +- port: 7007 ++ # The listener can also be expressed as a single : string. In this case we bind to ++ # all interfaces, the most permissive setting. The right value depends on your specific deployment. ++ listen: ':7007' ++ ++ # config options: https://node-postgres.com/api/client ++ database: ++ client: pg ++ connection: ++ host: ${POSTGRES_HOST} ++ port: ${POSTGRES_PORT} ++ user: ${POSTGRES_USER} ++ password: ${POSTGRES_PASSWORD} ++ # https://node-postgres.com/features/ssl ++ # you can set the sslmode configuration option via the `PGSSLMODE` environment variable ++ # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require) ++ # ssl: ++ # ca: # if you have a CA file and want to verify it you can uncomment this section ++ # $file: /ca/server.crt ++ ++catalog: ++ # Overrides the default list locations from app-config.yaml as these contain example data. ++ # See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details ++ # on how to get entities into the catalog. ++ locations: [] +diff --git a/app-config.yaml b/app-config.yaml +index aac1eb1..08d2abf 100644 +--- a/app-config.yaml ++++ b/app-config.yaml +@@ -8,2 +8,8 @@ organization: + backend: ++ # Used for enabling authentication, secret is shared by all backend plugins ++ # See https://backstage.io/docs/auth/service-to-service-auth for ++ # information on the format ++ # auth: ++ # keys: ++ # - secret: ${BACKEND_SECRET} + baseUrl: http://localhost:7007 +@@ -11,2 +17,4 @@ backend: + port: 7007 ++ # Uncomment the following host directive to bind to specific interfaces ++ # host: 127.0.0.1 + csp: +@@ -17,9 +25,9 @@ backend: + origin: http://localhost:3000 +- methods: [GET, POST, PUT, DELETE] ++ methods: [GET, HEAD, PATCH, POST, PUT, DELETE] + credentials: true ++ # This is for local development only, it is not recommended to use this in production ++ # The production database configuration is stored in app-config.production.yaml + database: +- client: sqlite3 ++ client: better-sqlite3 + connection: ':memory:' +- cache: +- store: memory + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir +@@ -29,2 +37,4 @@ integrations: + - host: github.com ++ # This is a Personal Access Token or PAT from GitHub. You can find out how to generate this token, and more information ++ # about setting up the GitHub integration here: https://backstage.io/docs/getting-started/configuration#setting-up-a-github-integration + token: ${GITHUB_TOKEN} +@@ -36,5 +46,8 @@ integrations: + proxy: +- '/test': +- target: 'https://example.com' +- changeOrigin: true ++ ### Example for how to add a proxy endpoint for the frontend. ++ ### A typical reason to do this is to handle HTTPS and CORS for internal services. ++ # endpoints: ++ # '/test': ++ # target: 'https://example.com' ++ # changeOrigin: true + +@@ -56,46 +69,35 @@ auth: + scaffolder: +- github: +- token: ${GITHUB_TOKEN} +- visibility: public # or 'internal' or 'private' ++ # see https://backstage.io/docs/features/software-templates/configuration for software template options + + catalog: ++ import: ++ entityFilename: catalog-info.yaml ++ pullRequestBranchName: backstage-integration + rules: +- - allow: [Component, System, API, Group, User, Resource, Location] ++ - allow: [Component, System, API, Resource, Location] + locations: +- # Backstage example components +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-components.yaml +- +- # Backstage example systems +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-systems.yaml +- +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml +- +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml ++ # Local example data, file locations are relative to the backend process, typically `packages/backend` ++ - type: file ++ target: ../../examples/entities.yaml + +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.yaml +- +- # Backstage example templates +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/react-ssr-template/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/springboot-grpc-template/template.yaml ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml + rules: + - allow: [Template] +- - type: url +- target: https://github.com/spotify/cookiecutter-golang/blob/master/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/docs-template/template.yaml ++ ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml + rules: +- - allow: [Template] ++ - allow: [User, Group] ++ ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml ++ ++ ## Uncomment these lines to add an example org ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme-corp.yaml ++ # rules: ++ # - allow: [User, Group] +diff --git a/backstage.json b/backstage.json +index 77e9232..707af29 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "0.4.5" ++ "version": "1.20.0-next.1" + } +diff --git a/examples/entities.yaml b/examples/entities.yaml +new file mode 100644 +index 0000000..447e8b1 +--- /dev/null ++++ b/examples/entities.yaml +@@ -0,0 +1,41 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system ++apiVersion: backstage.io/v1alpha1 ++kind: System ++metadata: ++ name: examples ++spec: ++ owner: guests ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: example-website ++spec: ++ type: website ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ providesApis: [example-grpc-api] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api ++apiVersion: backstage.io/v1alpha1 ++kind: API ++metadata: ++ name: example-grpc-api ++spec: ++ type: grpc ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ definition: | ++ syntax = "proto3"; ++ ++ service Exampler { ++ rpc Example (ExampleMessage) returns (ExampleMessage) {}; ++ } ++ ++ message ExampleMessage { ++ string example = 1; ++ }; +diff --git a/examples/org.yaml b/examples/org.yaml +new file mode 100644 +index 0000000..a10e81f +--- /dev/null ++++ b/examples/org.yaml +@@ -0,0 +1,17 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user ++apiVersion: backstage.io/v1alpha1 ++kind: User ++metadata: ++ name: guest ++spec: ++ memberOf: [guests] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group ++apiVersion: backstage.io/v1alpha1 ++kind: Group ++metadata: ++ name: guests ++spec: ++ type: team ++ children: [] +diff --git a/examples/template/content/catalog-info.yaml b/examples/template/content/catalog-info.yaml +new file mode 100644 +index 0000000..d4ccca4 +--- /dev/null ++++ b/examples/template/content/catalog-info.yaml +@@ -0,0 +1,8 @@ ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: ${{ values.name | dump }} ++spec: ++ type: service ++ owner: user:guest ++ lifecycle: experimental +diff --git a/examples/template/content/index.js b/examples/template/content/index.js +new file mode 100644 +index 0000000..071ce5a +--- /dev/null ++++ b/examples/template/content/index.js +@@ -0,0 +1 @@ ++console.log('Hello from ${{ values.name }}!'); +diff --git a/examples/template/content/package.json b/examples/template/content/package.json +new file mode 100644 +index 0000000..86f968a +--- /dev/null ++++ b/examples/template/content/package.json +@@ -0,0 +1,5 @@ ++{ ++ "name": "${{ values.name }}", ++ "private": true, ++ "dependencies": {} ++} +diff --git a/examples/template/template.yaml b/examples/template/template.yaml +new file mode 100644 +index 0000000..33f262b +--- /dev/null ++++ b/examples/template/template.yaml +@@ -0,0 +1,74 @@ ++apiVersion: scaffolder.backstage.io/v1beta3 ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template ++kind: Template ++metadata: ++ name: example-nodejs-template ++ title: Example Node.js Template ++ description: An example template for the scaffolder that creates a simple Node.js service ++spec: ++ owner: user:guest ++ type: service ++ ++ # These parameters are used to generate the input form in the frontend, and are ++ # used to gather input data for the execution of the template. ++ parameters: ++ - title: Fill in some steps ++ required: ++ - name ++ properties: ++ name: ++ title: Name ++ type: string ++ description: Unique name of the component ++ ui:autofocus: true ++ ui:options: ++ rows: 5 ++ - title: Choose a location ++ required: ++ - repoUrl ++ properties: ++ repoUrl: ++ title: Repository Location ++ type: string ++ ui:field: RepoUrlPicker ++ ui:options: ++ allowedHosts: ++ - github.com ++ ++ # These steps are executed in the scaffolder backend, using data that we gathered ++ # via the parameters above. ++ steps: ++ # Each step executes an action, in this case one templates files into the working directory. ++ - id: fetch-base ++ name: Fetch Base ++ action: fetch:template ++ input: ++ url: ./content ++ values: ++ name: ${{ parameters.name }} ++ ++ # This step publishes the contents of the working directory to GitHub. ++ - id: publish ++ name: Publish ++ action: publish:github ++ input: ++ allowedHosts: ['github.com'] ++ description: This is ${{ parameters.name }} ++ repoUrl: ${{ parameters.repoUrl }} ++ ++ # The final step is to register our new component in the catalog. ++ - id: register ++ name: Register ++ action: catalog:register ++ input: ++ repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} ++ catalogInfoPath: '/catalog-info.yaml' ++ ++ # Outputs are displayed to the user after a successful execution of the template. ++ output: ++ links: ++ - title: Repository ++ url: ${{ steps['publish'].output.remoteUrl }} ++ - title: Open in catalog ++ icon: catalog ++ entityRef: ${{ steps['register'].output.entityRef }} +diff --git a/lerna.json b/lerna.json +index 322929d..529a62f 100644 +--- a/lerna.json ++++ b/lerna.json +@@ -3,4 +3,4 @@ + "npmClient": "yarn", +- "useWorkspaces": true, +- "version": "0.1.0" ++ "version": "0.1.0", ++ "$schema": "node_modules/lerna/schemas/lerna-schema.json" + } +diff --git a/package.json b/package.json +index 869d117..2364da8 100644 +--- a/package.json ++++ b/package.json +@@ -5,3 +5,3 @@ + "engines": { +- "node": "14 || 16" ++ "node": "18 || 20" + }, +@@ -11,3 +11,4 @@ + "start-backend": "yarn workspace backend start", +- "build": "lerna run build", ++ "build:backend": "yarn workspace backend build", ++ "build:all": "backstage-cli repo build --all", + "build-image": "yarn workspace backend build-image", +@@ -15,11 +16,11 @@ + "tsc:full": "tsc --skipLibCheck false --incremental false", +- "clean": "backstage-cli clean && lerna run clean", +- "diff": "lerna run diff --", +- "test": "backstage-cli test", +- "test:all": "lerna run test -- --coverage", +- "lint": "lerna run lint --since origin/master --", +- "lint:all": "lerna run lint --", ++ "clean": "backstage-cli repo clean", ++ "test": "backstage-cli repo test", ++ "test:all": "backstage-cli repo test --coverage", ++ "test:e2e": "playwright test", ++ "fix": "backstage-cli repo fix", ++ "lint": "backstage-cli repo lint --since origin/master", ++ "lint:all": "backstage-cli repo lint", + "prettier:check": "prettier --check .", +- "create-plugin": "backstage-cli create-plugin --scope internal", +- "remove-plugin": "backstage-cli remove-plugin" ++ "new": "backstage-cli new --scope internal" + }, +@@ -32,7 +33,15 @@ + "devDependencies": { +- "@backstage/cli": "^0.9.1", +- "@spotify/prettier-config": "^11.0.0", +- "concurrently": "^6.0.0", +- "lerna": "^4.0.0", +- "prettier": "^2.3.2" ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/e2e-test-utils": "^0.1.0", ++ "@playwright/test": "^1.32.3", ++ "@spotify/prettier-config": "^12.0.0", ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^9.0.0", ++ "prettier": "^2.3.2", ++ "typescript": "~5.2.0" ++ }, ++ "resolutions": { ++ "@types/react": "^17", ++ "@types/react-dom": "^17" + }, +@@ -40,3 +49,3 @@ + "lint-staged": { +- "*.{js,jsx,ts,tsx}": [ ++ "*.{js,jsx,ts,tsx,mjs,cjs}": [ + "eslint --fix", +diff --git a/packages/README.md b/packages/README.md +new file mode 100644 +index 0000000..6327fa0 +--- /dev/null ++++ b/packages/README.md +@@ -0,0 +1,9 @@ ++# The Packages Folder ++ ++This is where your own applications and centrally managed libraries live, each ++in a separate folder of its own. ++ ++From the start there's an `app` folder (for the frontend) and a `backend` folder ++(for the Node backend), but you can also add more modules in here that house ++your core additions and adaptations, such as themes, common React component ++libraries, utilities, and similar. +diff --git a/packages/app/.eslintignore b/packages/app/.eslintignore +new file mode 100644 +index 0000000..a48cf0d +--- /dev/null ++++ b/packages/app/.eslintignore +@@ -0,0 +1 @@ ++public +diff --git a/packages/app/.eslintrc.js b/packages/app/.eslintrc.js +index 13573ef..e2a53a6 100644 +--- a/packages/app/.eslintrc.js ++++ b/packages/app/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/app/cypress.json b/packages/app/cypress.json +deleted file mode 100644 +index 5de7ebf..0000000 +--- a/packages/app/cypress.json ++++ /dev/null +@@ -1,5 +0,0 @@ +-{ +- "baseUrl": "http://localhost:3001", +- "fixturesFolder": false, +- "pluginsFile": false +-} +diff --git a/packages/app/cypress/.eslintrc.json b/packages/app/cypress/.eslintrc.json +deleted file mode 100644 +index 2b3a458..0000000 +--- a/packages/app/cypress/.eslintrc.json ++++ /dev/null +@@ -1,21 +0,0 @@ +-{ +- "plugins": ["cypress"], +- "extends": ["plugin:cypress/recommended"], +- "rules": { +- "jest/expect-expect": [ +- "error", +- { +- "assertFunctionNames": ["expect", "cy.contains"] +- } +- ], +- "import/no-extraneous-dependencies": [ +- "error", +- { +- "devDependencies": true, +- "optionalDependencies": true, +- "peerDependencies": true, +- "bundledDependencies": true +- } +- ] +- } +-} +diff --git a/packages/app/cypress/integration/app.js b/packages/app/cypress/integration/app.js +deleted file mode 100644 +index 43fb2e3..0000000 +--- a/packages/app/cypress/integration/app.js ++++ /dev/null +@@ -1,6 +0,0 @@ +-describe('App', () => { +- it('should render the catalog', () => { +- cy.visit('/'); +- cy.contains('My Company Catalog'); +- }); +-}); +diff --git a/packages/app/e2e-tests/app.test.ts b/packages/app/e2e-tests/app.test.ts +new file mode 100644 +index 0000000..d45bc0d +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,23 @@ ++/* ++ * Copyright 2020 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { test, expect } from '@playwright/test'; ++ ++test('App should render the welcome page', async ({ page }) => { ++ await page.goto('/'); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 6d87eb0..2100405 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,23 +5,38 @@ + "bundled": true, ++ "backstage": { ++ "role": "frontend" ++ }, ++ "scripts": { ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "clean": "backstage-cli package clean", ++ "test": "backstage-cli package test", ++ "lint": "backstage-cli package lint" ++ }, + "dependencies": { +- "@backstage/app-defaults": "^0.1.1", +- "@backstage/catalog-model": "^0.9.7", +- "@backstage/cli": "^0.9.1", +- "@backstage/core-app-api": "^0.1.23", +- "@backstage/core-components": "^0.7.5", +- "@backstage/core-plugin-api": "^0.2.1", +- "@backstage/integration-react": "^0.1.14", +- "@backstage/plugin-api-docs": "^0.6.15", +- "@backstage/plugin-catalog": "^0.7.3", +- "@backstage/plugin-catalog-import": "^0.7.4", +- "@backstage/plugin-catalog-react": "^0.6.4", +- "@backstage/plugin-github-actions": "^0.4.25", +- "@backstage/plugin-org": "^0.3.29", +- "@backstage/plugin-scaffolder": "^0.11.12", +- "@backstage/plugin-search": "^0.4.18", +- "@backstage/plugin-tech-radar": "^0.4.12", +- "@backstage/plugin-techdocs": "^0.12.7", +- "@backstage/plugin-user-settings": "^0.3.11", +- "@backstage/test-utils": "^0.1.23", +- "@backstage/theme": "^0.2.13", ++ "@backstage/app-defaults": "^1.4.5-next.1", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/core-app-api": "^1.11.1-next.0", ++ "@backstage/core-components": "^0.13.8-next.1", ++ "@backstage/core-plugin-api": "^1.8.0-next.0", ++ "@backstage/integration-react": "^1.1.21-next.1", ++ "@backstage/plugin-api-docs": "^0.9.13-next.1", ++ "@backstage/plugin-catalog": "^1.15.0-next.1", ++ "@backstage/plugin-catalog-common": "^1.0.17", ++ "@backstage/plugin-catalog-graph": "^0.2.38-next.1", ++ "@backstage/plugin-catalog-import": "^0.10.2-next.1", ++ "@backstage/plugin-catalog-react": "^1.9.0-next.1", ++ "@backstage/plugin-github-actions": "^0.6.7-next.1", ++ "@backstage/plugin-org": "^0.6.16-next.1", ++ "@backstage/plugin-permission-react": "^0.4.17-next.0", ++ "@backstage/plugin-scaffolder": "^1.16.0-next.1", ++ "@backstage/plugin-search": "^1.4.2-next.1", ++ "@backstage/plugin-search-react": "^1.7.2-next.1", ++ "@backstage/plugin-tech-radar": "^0.6.10-next.1", ++ "@backstage/plugin-techdocs": "^1.8.1-next.1", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.2-next.1", ++ "@backstage/plugin-techdocs-react": "^1.1.13-next.1", ++ "@backstage/plugin-user-settings": "^0.7.12-next.1", ++ "@backstage/theme": "^0.4.4-next.0", + "@material-ui/core": "^4.12.2", +@@ -29,30 +44,17 @@ + "history": "^5.0.0", +- "react": "^16.13.1", +- "react-dom": "^16.13.1", +- "react-router": "6.0.0-beta.0", +- "react-router-dom": "6.0.0-beta.0", +- "react-use": "^15.3.3" ++ "react": "^17.0.2", ++ "react-dom": "^17.0.2", ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { ++ "@backstage/test-utils": "^1.4.5-next.0", ++ "@playwright/test": "^1.32.3", + "@testing-library/jest-dom": "^5.10.1", +- "@testing-library/react": "^10.4.1", +- "@testing-library/user-event": "^12.0.7", +- "@types/jest": "^26.0.7", +- "@types/node": "^14.14.32", ++ "@testing-library/react": "^12.1.3", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^8.0.0", + "@types/react-dom": "*", +- "cross-env": "^7.0.0", +- "cypress": "^7.3.0", +- "eslint-plugin-cypress": "^2.10.3", +- "start-server-and-test": "^1.10.11" +- }, +- "scripts": { +- "start": "backstage-cli app:serve", +- "build": "backstage-cli app:build", +- "test": "backstage-cli test", +- "lint": "backstage-cli lint", +- "clean": "backstage-cli clean", +- "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", +- "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", +- "cy:dev": "cypress open", +- "cy:run": "cypress run" ++ "cross-env": "^7.0.0" + }, +diff --git a/packages/app/public/index.html b/packages/app/public/index.html +index b93896c..fc758ee 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -10,3 +10,2 @@ + /> +- + +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 8a53583..8d62f29 100644 +--- a/packages/app/src/App.tsx ++++ b/packages/app/src/App.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { Navigate, Route } from 'react-router'; ++import { Navigate, Route } from 'react-router-dom'; + import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; +@@ -13,2 +13,3 @@ import { + import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; ++import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +@@ -16,3 +17,2 @@ import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -21,2 +21,4 @@ import { + } from '@backstage/plugin-techdocs'; ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; + import { UserSettingsPage } from '@backstage/plugin-user-settings'; +@@ -29,3 +31,6 @@ import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; + import { createApp } from '@backstage/app-defaults'; +-import { FlatRoutes } from '@backstage/core-app-api'; ++import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; ++import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; ++import { RequirePermission } from '@backstage/plugin-permission-react'; ++import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; + +@@ -37,5 +42,6 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); + bind(apiDocsPlugin.externalRoutes, { +- createComponent: scaffolderPlugin.routes.root, ++ registerApi: catalogImportPlugin.routes.importPage, + }); +@@ -43,2 +49,6 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, ++ }); ++ bind(orgPlugin.externalRoutes, { ++ catalogIndex: catalogPlugin.routes.catalogIndex, + }); +@@ -47,8 +57,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -60,5 +67,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -73,3 +82,10 @@ const routes = ( + /> +- } /> ++ ++ ++ ++ } ++ /> + }> +@@ -78,2 +94,3 @@ const routes = ( + } /> ++ } /> + +@@ -81,4 +98,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -88,5 +105,3 @@ const App = () => ( + +- ++ , + ); +- +-export default App; +diff --git a/packages/app/src/components/Root/LogoFull.tsx b/packages/app/src/components/Root/LogoFull.tsx +index c7b1c84..47e3b73 100644 +--- a/packages/app/src/components/Root/LogoFull.tsx ++++ b/packages/app/src/components/Root/LogoFull.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/LogoIcon.tsx b/packages/app/src/components/Root/LogoIcon.tsx +index 073cf6e..7eae8c7 100644 +--- a/packages/app/src/components/Root/LogoIcon.tsx ++++ b/packages/app/src/components/Root/LogoIcon.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx +index ec59b0b..6768b48 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,19 +1,3 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- +-import React, { useContext, PropsWithChildren } from 'react'; +-import { Link, makeStyles } from '@material-ui/core'; ++import React, { PropsWithChildren } from 'react'; ++import { makeStyles } from '@material-ui/core'; + import HomeIcon from '@material-ui/icons/Home'; +@@ -25,4 +9,6 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; +-import { Settings as SidebarSettings } from '@backstage/plugin-user-settings'; ++import { ++ Settings as SidebarSettings, ++ UserSettingsSignInAvatar, ++} from '@backstage/plugin-user-settings'; + import { SidebarSearchModal } from '@backstage/plugin-search'; +@@ -30,10 +16,14 @@ import { + Sidebar, +- SidebarPage, + sidebarConfig, +- SidebarContext, +- SidebarItem, + SidebarDivider, +- SidebarSpace, ++ SidebarGroup, ++ SidebarItem, ++ SidebarPage, + SidebarScrollWrapper, ++ SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; ++import MenuIcon from '@material-ui/icons/Menu'; ++import SearchIcon from '@material-ui/icons/Search'; + +@@ -56,3 +46,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -60,8 +50,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -76,17 +61,27 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ } to="/search"> ++ ++ + +- {/* Global nav, not org-specific */} +- +- +- +- +- {/* End global nav */} +- +- +- +- ++ }> ++ {/* Global nav, not org-specific */} ++ ++ ++ ++ ++ {/* End global nav */} ++ ++ ++ ++ ++ + + +- ++ } ++ to="/settings" ++ > ++ ++ + +diff --git a/packages/app/src/components/Root/index.ts b/packages/app/src/components/Root/index.ts +index dff706f..3528881 100644 +--- a/packages/app/src/components/Root/index.ts ++++ b/packages/app/src/components/Root/index.ts +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + export { Root } from './Root'; +diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx +index d3b4b78..6722ea2 100644 +--- a/packages/app/src/components/catalog/EntityPage.tsx ++++ b/packages/app/src/components/catalog/EntityPage.tsx +@@ -1,16 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ + import React from 'react'; +@@ -29,3 +14,2 @@ import { + EntityDependsOnResourcesCard, +- EntitySystemDiagramCard, + EntityHasComponentsCard, +@@ -43,2 +27,4 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +@@ -56,2 +42,27 @@ import { EntityTechdocsContent } from '@backstage/plugin-techdocs'; + import { EmptyState } from '@backstage/core-components'; ++import { ++ Direction, ++ EntityCatalogGraphCard, ++} from '@backstage/plugin-catalog-graph'; ++import { ++ RELATION_API_CONSUMED_BY, ++ RELATION_API_PROVIDED_BY, ++ RELATION_CONSUMES_API, ++ RELATION_DEPENDENCY_OF, ++ RELATION_DEPENDS_ON, ++ RELATION_HAS_PART, ++ RELATION_PART_OF, ++ RELATION_PROVIDES_API, ++} from '@backstage/catalog-model'; ++ ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); + +@@ -94,2 +105,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -110,2 +129,6 @@ const overviewContent = ( + ++ ++ ++ ++ + +@@ -152,3 +175,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -179,3 +202,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -198,3 +221,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -225,2 +248,5 @@ const apiPage = ( + ++ ++ ++ + +@@ -292,3 +318,9 @@ const systemPage = ( + +- ++ ++ ++ ++ ++ ++ ++ + +@@ -304,3 +336,19 @@ const systemPage = ( + +- ++ + +@@ -317,2 +365,5 @@ const domainPage = ( + ++ ++ ++ + +diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx +index 50ffbad..1788dde 100644 +--- a/packages/app/src/components/search/SearchPage.tsx ++++ b/packages/app/src/components/search/SearchPage.tsx +@@ -1,7 +1,12 @@ + import React from 'react'; +-import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; ++import { makeStyles, Theme, Grid, Paper } from '@material-ui/core'; + +-import { CatalogResultListItem } from '@backstage/plugin-catalog'; +-import { DocsResultListItem } from '@backstage/plugin-techdocs'; ++import { CatalogSearchResultListItem } from '@backstage/plugin-catalog'; ++import { ++ catalogApiRef, ++ CATALOG_FILTER_EXISTS, ++} from '@backstage/plugin-catalog-react'; ++import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; + ++import { SearchType } from '@backstage/plugin-search'; + import { +@@ -10,6 +15,13 @@ import { + SearchResult, +- SearchType, +- DefaultResultListItem, +-} from '@backstage/plugin-search'; +-import { Content, Header, Page } from '@backstage/core-components'; ++ SearchPagination, ++ useSearch, ++} from '@backstage/plugin-search-react'; ++import { ++ CatalogIcon, ++ Content, ++ DocsIcon, ++ Header, ++ Page, ++} from '@backstage/core-components'; ++import { useApi } from '@backstage/core-plugin-api'; + +@@ -21,2 +33,3 @@ const useStyles = makeStyles((theme: Theme) => ({ + padding: theme.spacing(2), ++ marginTop: theme.spacing(2), + }, +@@ -31,2 +44,4 @@ const SearchPage = () => { + const classes = useStyles(); ++ const { types } = useSearch(); ++ const catalogApi = useApi(catalogApiRef); + +@@ -39,3 +54,3 @@ const SearchPage = () => { + +- ++ + +@@ -43,10 +58,43 @@ const SearchPage = () => { + ++ , ++ }, ++ { ++ value: 'techdocs', ++ name: 'Documentation', ++ icon: , ++ }, ++ ]} ++ /> + +- ++ {types.includes('techdocs') && ( ++ { ++ // Return a list of entities which are documented. ++ const { items } = await catalogApi.getEntities({ ++ fields: ['metadata.name'], ++ filter: { ++ 'metadata.annotations.backstage.io/techdocs-ref': ++ CATALOG_FILTER_EXISTS, ++ }, ++ }); ++ ++ const names = items.map(entity => entity.metadata.name); ++ names.sort(); ++ return names; ++ }} ++ /> ++ )} + { + className={classes.filter} ++ label="Lifecycle" + name="lifecycle" +@@ -62,32 +111,6 @@ const SearchPage = () => { + ++ + +- {({ results }) => ( +- +- {results.map(({ type, document }) => { +- switch (type) { +- case 'software-catalog': +- return ( +- +- ); +- case 'techdocs': +- return ( +- +- ); +- default: +- return ( +- +- ); +- } +- })} +- +- )} ++ } /> ++ } /> + +diff --git a/packages/backend/.eslintrc.js b/packages/backend/.eslintrc.js +index 16a033d..e2a53a6 100644 +--- a/packages/backend/.eslintrc.js ++++ b/packages/backend/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint.backend')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index 31231a3..18548e9 100644 +--- a/packages/backend/Dockerfile ++++ b/packages/backend/Dockerfile +@@ -7,3 +7,3 @@ + # yarn tsc +-# yarn build ++# yarn build:backend + # +@@ -11,6 +11,29 @@ + +-FROM node:14-buster-slim ++FROM node:18-bookworm-slim + ++# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends python3 g++ build-essential && \ ++ yarn config set python /usr/bin/python3 ++ ++# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, ++# in which case you should also move better-sqlite3 to "devDependencies" in package.json. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends libsqlite3-dev ++ ++# From here on we use the least-privileged `node` user to run the backend. ++USER node ++ ++# This should create the app dir as `node`. ++# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. ++# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. + WORKDIR /app + ++# This switches many Node.js dependencies to production mode. ++ENV NODE_ENV production ++ + # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. +@@ -18,11 +41,12 @@ WORKDIR /app + # and along with yarn.lock and the root package.json, that's enough to run yarn install. +-COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ ++COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ + RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz + +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + + # Then copy the rest of the backend bundle, along with any other files we might want. +-COPY packages/backend/dist/bundle.tar.gz app-config.yaml ./ ++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"] +diff --git a/packages/backend/README.md b/packages/backend/README.md +index 02426ef..3607b0a 100644 +--- a/packages/backend/README.md ++++ b/packages/backend/README.md +@@ -18,4 +18,2 @@ To run the example backend, first go to the project root and run + yarn install +-yarn tsc +-yarn build + ``` +@@ -27,7 +25,2 @@ After that, go to the `packages/backend` directory and run + ```bash +-AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x \ +-AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x \ +-AUTH_OAUTH2_CLIENT_ID=x AUTH_OAUTH2_CLIENT_SECRET=x \ +-AUTH_OAUTH2_AUTH_URL=x AUTH_OAUTH2_TOKEN_URL=x \ +-LOG_LEVEL=debug \ + yarn start +@@ -35,4 +28,4 @@ yarn start + +-Substitute `x` for actual values, or leave them as dummy values just to try out +-the backend without using the auth or sentry features. ++If you want to override any configuration locally, for example adding any secrets, ++you can do so in `app-config.local.yaml`. + +@@ -45,3 +38,3 @@ locations to the backend. These are places where the backend can find some + entity descriptor data to consume and serve. For more information, see +-[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog). ++[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). + +@@ -65,2 +58,2 @@ and + - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +-- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) ++- [Backstage Documentation](https://backstage.io/docs) +diff --git a/packages/backend/package.json b/packages/backend/package.json +index f5c3b2e..f224eaf 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,27 +6,33 @@ + "private": true, ++ "backstage": { ++ "role": "backend" ++ }, + "scripts": { +- "build": "backstage-cli backend:bundle", +- "build-image": "docker build ../.. -f Dockerfile --tag backstage", +- "start": "backstage-cli backend:dev", +- "lint": "backstage-cli lint", +- "test": "backstage-cli test", +- "clean": "backstage-cli clean", +- "migrate:create": "knex migrate:make -x ts" ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "lint": "backstage-cli package lint", ++ "test": "backstage-cli package test", ++ "clean": "backstage-cli package clean", ++ "build-image": "docker build ../.. -f Dockerfile --tag backstage" + }, + "dependencies": { +- "app": "0.0.0", +- "@backstage/backend-common": "^0.9.11", +- "@backstage/catalog-model": "^0.9.7", +- "@backstage/catalog-client": "^0.5.2", +- "@backstage/config": "^0.1.11", +- "@backstage/plugin-app-backend": "^0.3.19", +- "@backstage/plugin-auth-backend": "^0.4.9", +- "@backstage/plugin-catalog-backend": "^0.18.0", +- "@backstage/plugin-proxy-backend": "^0.2.14", +- "@backstage/plugin-scaffolder-backend": "^0.15.14", +- "@backstage/plugin-search-backend": "^0.2.7", +- "@backstage/plugin-search-backend-node": "^0.4.3", +- "@backstage/plugin-techdocs-backend": "^0.10.9", +- "@gitbeaker/node": "^34.6.0", +- "@octokit/rest": "^18.5.3", ++ "@backstage/backend-common": "^0.19.9-next.1", ++ "@backstage/backend-tasks": "^0.5.12-next.1", ++ "@backstage/catalog-client": "^1.4.5", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/config": "^1.1.1", ++ "@backstage/plugin-app-backend": "^0.3.55-next.1", ++ "@backstage/plugin-auth-backend": "^0.20.0-next.1", ++ "@backstage/plugin-auth-node": "^0.4.1-next.1", ++ "@backstage/plugin-catalog-backend": "^1.15.0-next.1", ++ "@backstage/plugin-permission-common": "^0.7.9", ++ "@backstage/plugin-permission-node": "^0.7.18-next.1", ++ "@backstage/plugin-proxy-backend": "^0.4.5-next.1", ++ "@backstage/plugin-scaffolder-backend": "^1.19.0-next.1", ++ "@backstage/plugin-search-backend": "^1.4.7-next.1", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.16-next.1", ++ "@backstage/plugin-search-backend-node": "^1.2.11-next.1", ++ "@backstage/plugin-techdocs-backend": "^1.9.0-next.1", ++ "app": "link:../app", ++ "better-sqlite3": "^8.0.0", + "dockerode": "^3.3.1", +@@ -34,4 +40,3 @@ + "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "pg": "^8.3.0", + "winston": "^3.2.1" +@@ -39,6 +44,7 @@ + "devDependencies": { +- "@backstage/cli": "^0.9.1", ++ "@backstage/cli": "^0.24.0-next.1", + "@types/dockerode": "^3.3.0", + "@types/express": "^4.17.6", +- "@types/express-serve-static-core": "^4.17.5" ++ "@types/express-serve-static-core": "^4.17.5", ++ "@types/luxon": "^2.0.4" + }, +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index f2b14b2..04c4ff9 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -17,5 +17,7 @@ import { + DatabaseManager, +- SingleHostDiscovery, ++ HostDiscovery, + UrlReaders, ++ ServerTokenManager, + } from '@backstage/backend-common'; ++import { TaskScheduler } from '@backstage/backend-tasks'; + import { Config } from '@backstage/config'; +@@ -29,2 +31,4 @@ import search from './plugins/search'; + import { PluginEnvironment } from './types'; ++import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; + +@@ -33,8 +37,17 @@ function makeCreateEnv(config: Config) { + const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++ const discovery = HostDiscovery.fromConfig(config); ++ const cacheManager = CacheManager.fromConfig(config); ++ const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); ++ const tokenManager = ServerTokenManager.noop(); ++ const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); + +- root.info(`Created UrlReader ${reader}`); ++ const identity = DefaultIdentityClient.create({ ++ discovery, ++ }); ++ const permissions = ServerPermissionClient.fromConfig(config, { ++ discovery, ++ tokenManager, ++ }); + +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++ root.info(`Created UrlReader ${reader}`); + +@@ -44,3 +57,15 @@ function makeCreateEnv(config: Config) { + const cache = cacheManager.forPlugin(plugin); +- return { logger, database, cache, config, reader, discovery }; ++ const scheduler = taskScheduler.forPlugin(plugin); ++ return { ++ logger, ++ database, ++ cache, ++ config, ++ reader, ++ discovery, ++ tokenManager, ++ scheduler, ++ permissions, ++ identity, ++ }; + }; +@@ -70,2 +95,4 @@ async function main() { + apiRouter.use('/search', await search(searchEnv)); ++ ++ // Add backends ABOVE this line; this 404 handler is the catch-all fallback + apiRouter.use(notFoundHandler()); +@@ -85,3 +112,3 @@ module.hot?.accept(); + main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); ++ console.error('Backend failed to start up', error); + process.exit(1); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +index 07fb04f..7c37f68 100644 +--- a/packages/backend/src/plugins/app.ts ++++ b/packages/backend/src/plugins/app.ts +@@ -4,9 +4,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + return await createRouter({ +- logger, +- config, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, + appPackageName: 'app', +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +index 5216510..77eb6aa 100644 +--- a/packages/backend/src/plugins/auth.ts ++++ b/packages/backend/src/plugins/auth.ts +@@ -1,2 +1,6 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; ++import { ++ createRouter, ++ providers, ++ defaultAuthProviderFactories, ++} from '@backstage/plugin-auth-backend'; + import { Router } from 'express'; +@@ -4,9 +8,47 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, database, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, ++ providerFactories: { ++ ...defaultAuthProviderFactories, ++ ++ // This replaces the default GitHub auth provider with a customized one. ++ // The `signIn` option enables sign-in for this provider, using the ++ // identity resolution logic that's provided in the `resolver` callback. ++ // ++ // This particular resolver makes all users share a single "guest" identity. ++ // It should only be used for testing and trying out Backstage. ++ // ++ // If you want to use a production ready resolver you can switch to ++ // the one that is commented out below, it looks up a user entity in the ++ // catalog using the GitHub username of the authenticated user. ++ // That resolver requires you to have user entities populated in the catalog, ++ // for example using https://backstage.io/docs/integrations/github/org ++ // ++ // There are other resolvers to choose from, and you can also create ++ // your own, see the auth documentation for more details: ++ // ++ // https://backstage.io/docs/auth/identity-resolver ++ github: providers.github.create({ ++ signIn: { ++ resolver(_, ctx) { ++ const userRef = 'user:default/guest'; // Must be a full entity reference ++ return ctx.issueToken({ ++ claims: { ++ sub: userRef, // The user's own identity ++ ent: [userRef], // A list of identities that the user claims ownership through ++ }, ++ }); ++ }, ++ // resolver: providers.github.resolvers.usernameMatchingUserEntityName(), ++ }, ++ }), ++ }, ++ }); + } +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +index 506f6d9..54ec393 100644 +--- a/packages/backend/src/plugins/proxy.ts ++++ b/packages/backend/src/plugins/proxy.ts +@@ -4,8 +4,10 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ }); + } +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +index 6be2e97..a12fee2 100644 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ b/packages/backend/src/plugins/scaffolder.ts +@@ -1,5 +1,3 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; + import { CatalogClient } from '@backstage/catalog-client'; + import { createRouter } from '@backstage/plugin-scaffolder-backend'; +-import Docker from 'dockerode'; + import { Router } from 'express'; +@@ -7,20 +5,17 @@ import type { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- database, +- reader, +- discovery, +-}: PluginEnvironment): Promise { +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- const catalogClient = new CatalogClient({ discoveryApi: discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ const catalogClient = new CatalogClient({ ++ discoveryApi: env.discovery, ++ }); + + return await createRouter({ +- containerRunner, +- logger, +- config, +- database, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ reader: env.reader, + catalogClient, +- reader, ++ identity: env.identity, ++ permissions: env.permissions, + }); +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +index 63e1962..e9469dc 100644 +--- a/packages/backend/src/plugins/search.ts ++++ b/packages/backend/src/plugins/search.ts +@@ -7,13 +7,25 @@ import { + import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend'; ++import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend'; ++import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; ++import { Router } from 'express'; + +-export default async function createPlugin({ +- logger, +- discovery, +- config, +-}: PluginEnvironment) { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); ++ const searchEngine = new LunrSearchEngine({ ++ logger: env.logger, ++ }); ++ const indexBuilder = new IndexBuilder({ ++ logger: env.logger, ++ searchEngine, ++ }); ++ ++ const schedule = env.scheduler.createScheduledTaskRunner({ ++ frequency: { minutes: 10 }, ++ timeout: { minutes: 15 }, ++ // A 3 second delay gives the backend server a chance to initialize before ++ // any collators are executed, which may attempt requests against the API. ++ initialDelay: { seconds: 3 }, ++ }); + +@@ -22,4 +34,7 @@ export default async function createPlugin({ + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { discovery }), ++ schedule, ++ factory: DefaultCatalogCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, ++ }), + }); +@@ -28,4 +43,8 @@ export default async function createPlugin({ + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultTechDocsCollator.fromConfig(config, { discovery, logger }), ++ schedule, ++ factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ logger: env.logger, ++ tokenManager: env.tokenManager, ++ }), + }); +@@ -35,6 +54,4 @@ export default async function createPlugin({ + const { scheduler } = await indexBuilder.build(); ++ scheduler.start(); + +- // A 3 second delay gives the backend server a chance to initialize before +- // any collators are executed, which may attempt requests against the API. +- setTimeout(() => scheduler.start(), 3000); + useHotCleanup(module, () => scheduler.stop()); +@@ -43,3 +60,6 @@ export default async function createPlugin({ + engine: indexBuilder.getSearchEngine(), +- logger, ++ types: indexBuilder.getDocumentTypes(), ++ permissions: env.permissions, ++ config: env.config, ++ logger: env.logger, + }); +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +index 906d86d..be8bb0c 100644 +--- a/packages/backend/src/plugins/techdocs.ts ++++ b/packages/backend/src/plugins/techdocs.ts +@@ -11,12 +11,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, ++ const preparers = await Preparers.fromConfig(env.config, { ++ logger: env.logger, ++ reader: env.reader, + }); +@@ -28,4 +25,4 @@ export default async function createPlugin({ + // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, ++ const generators = await Generators.fromConfig(env.config, { ++ logger: env.logger, + containerRunner, +@@ -36,5 +33,5 @@ export default async function createPlugin({ + // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, ++ const publisher = await Publisher.fromConfig(env.config, { ++ logger: env.logger, ++ discovery: env.discovery, + }); +@@ -48,5 +45,6 @@ export default async function createPlugin({ + publisher, +- logger, +- config, +- discovery, ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ cache: env.cache, + }); +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +index 6c78a2a..9cd2c74 100644 +--- a/packages/backend/src/types.ts ++++ b/packages/backend/src/types.ts +@@ -6,4 +6,8 @@ import { + PluginEndpointDiscovery, ++ TokenManager, + UrlReader, + } from '@backstage/backend-common'; ++import { PluginTaskScheduler } from '@backstage/backend-tasks'; ++import { PermissionEvaluator } from '@backstage/plugin-permission-common'; ++import { IdentityApi } from '@backstage/plugin-auth-node'; + +@@ -16,2 +20,6 @@ export type PluginEnvironment = { + discovery: PluginEndpointDiscovery; ++ tokenManager: TokenManager; ++ scheduler: PluginTaskScheduler; ++ permissions: PermissionEvaluator; ++ identity: IdentityApi; + }; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..37c7fb1 +--- /dev/null ++++ b/playwright.config.ts +@@ -0,0 +1,60 @@ ++/* ++ * Copyright 2023 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { defineConfig } from '@playwright/test'; ++import { generateProjects } from '@backstage/e2e-test-utils/playwright'; ++ ++/** ++ * See https://playwright.dev/docs/test-configuration. ++ */ ++export default defineConfig({ ++ timeout: 60_000, ++ ++ expect: { ++ timeout: 5_000, ++ }, ++ ++ // Run your local dev server before starting the tests ++ webServer: process.env.CI ++ ? [] ++ : [ ++ { ++ command: 'yarn start', ++ port: 3000, ++ reuseExistingServer: true, ++ timeout: 60_000, ++ }, ++ ], ++ ++ forbidOnly: !!process.env.CI, ++ ++ retries: process.env.CI ? 2 : 0, ++ ++ reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]], ++ ++ use: { ++ actionTimeout: 0, ++ baseURL: ++ process.env.PLAYWRIGHT_URL ?? ++ (process.env.CI ? 'http://localhost:7007' : 'http://localhost:3000'), ++ screenshot: 'only-on-failure', ++ trace: 'on-first-retry', ++ }, ++ ++ outputDir: 'node_modules/.cache/e2e-test-results', ++ ++ projects: generateProjects(), // Find all packages with e2e-test folders ++}); +diff --git a/plugins/README.md b/plugins/README.md +new file mode 100644 +index 0000000..d7865fd +--- /dev/null ++++ b/plugins/README.md +@@ -0,0 +1,9 @@ ++# The Plugins Folder ++ ++This is where your own plugins and their associated modules live, each in a ++separate folder of its own. ++ ++If you want to create a new plugin here, go to your project root directory, run ++the command `yarn new`, and follow the on-screen instructions. ++ ++You can also check out existing plugins on [the plugin marketplace](https://backstage.io/plugins)! +diff --git a/yarn.lock b/yarn.lock +new file mode 100644 +index 0000000..5ad7fe2 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1 @@ ++# intentionally left empty diff --git a/diffs/0.4.6..0.5.7-next.1.diff b/diffs/0.4.6..0.5.7-next.1.diff new file mode 100644 index 00000000..86f4a852 --- /dev/null +++ b/diffs/0.4.6..0.5.7-next.1.diff @@ -0,0 +1,1913 @@ +diff --git a/.dockerignore b/.dockerignore +index 63c9c34..05edb62 100644 +--- a/.dockerignore ++++ b/.dockerignore +@@ -1,5 +1,8 @@ + .git ++.yarn/cache ++.yarn/install-state.gz + node_modules +-packages +-!packages/backend/dist ++packages/*/src ++packages/*/node_modules + plugins ++*.local.yaml +diff --git a/.eslintignore b/.eslintignore +new file mode 100644 +index 0000000..e5b1994 +--- /dev/null ++++ b/.eslintignore +@@ -0,0 +1 @@ ++playwright.config.ts +diff --git a/.gitignore b/.gitignore +index d16a8d3..fbf8139 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -17,2 +17,11 @@ node_modules/ + ++# Yarn 3 files ++.pnp.* ++.yarn/* ++!.yarn/patches ++!.yarn/plugins ++!.yarn/releases ++!.yarn/sdks ++!.yarn/versions ++ + # Node version directives +@@ -39 +48,7 @@ site + *-credentials.yaml ++ ++# vscode database functionality support files ++*.session.sql ++ ++# E2E test reports ++e2e-test-report/ +diff --git a/app-config.production.yaml b/app-config.production.yaml +index 5e36c23..8f0751c 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -1,3 +1,3 @@ + app: +- # Should be the same as backend.baseUrl when using the `app-backend` plugin ++ # Should be the same as backend.baseUrl when using the `app-backend` plugin. + baseUrl: http://localhost:7007 +@@ -5,4 +5,31 @@ app: + backend: ++ # Note that the baseUrl should be the URL that the browser and other clients ++ # should use when communicating with the backend, i.e. it needs to be ++ # reachable not just from within the backend host, but from all of your ++ # callers. When its value is "http://localhost:7007", it's strictly private ++ # and can't be reached by others. + baseUrl: http://localhost:7007 +- listen: +- port: 7007 ++ # The listener can also be expressed as a single : string. In this case we bind to ++ # all interfaces, the most permissive setting. The right value depends on your specific deployment. ++ listen: ':7007' ++ ++ # config options: https://node-postgres.com/api/client ++ database: ++ client: pg ++ connection: ++ host: ${POSTGRES_HOST} ++ port: ${POSTGRES_PORT} ++ user: ${POSTGRES_USER} ++ password: ${POSTGRES_PASSWORD} ++ # https://node-postgres.com/features/ssl ++ # you can set the sslmode configuration option via the `PGSSLMODE` environment variable ++ # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require) ++ # ssl: ++ # ca: # if you have a CA file and want to verify it you can uncomment this section ++ # $file: /ca/server.crt ++ ++catalog: ++ # Overrides the default list locations from app-config.yaml as these contain example data. ++ # See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details ++ # on how to get entities into the catalog. ++ locations: [] +diff --git a/app-config.yaml b/app-config.yaml +index 2999615..08d2abf 100644 +--- a/app-config.yaml ++++ b/app-config.yaml +@@ -9,3 +9,4 @@ backend: + # Used for enabling authentication, secret is shared by all backend plugins +- # See backend-to-backend-auth.md in the docs for information on the format ++ # See https://backstage.io/docs/auth/service-to-service-auth for ++ # information on the format + # auth: +@@ -16,2 +17,4 @@ backend: + port: 7007 ++ # Uncomment the following host directive to bind to specific interfaces ++ # host: 127.0.0.1 + csp: +@@ -22,9 +25,9 @@ backend: + origin: http://localhost:3000 +- methods: [GET, POST, PUT, DELETE] ++ methods: [GET, HEAD, PATCH, POST, PUT, DELETE] + credentials: true ++ # This is for local development only, it is not recommended to use this in production ++ # The production database configuration is stored in app-config.production.yaml + database: +- client: sqlite3 ++ client: better-sqlite3 + connection: ':memory:' +- cache: +- store: memory + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir +@@ -34,2 +37,4 @@ integrations: + - host: github.com ++ # This is a Personal Access Token or PAT from GitHub. You can find out how to generate this token, and more information ++ # about setting up the GitHub integration here: https://backstage.io/docs/getting-started/configuration#setting-up-a-github-integration + token: ${GITHUB_TOKEN} +@@ -41,5 +46,8 @@ integrations: + proxy: +- '/test': +- target: 'https://example.com' +- changeOrigin: true ++ ### Example for how to add a proxy endpoint for the frontend. ++ ### A typical reason to do this is to handle HTTPS and CORS for internal services. ++ # endpoints: ++ # '/test': ++ # target: 'https://example.com' ++ # changeOrigin: true + +@@ -61,46 +69,35 @@ auth: + scaffolder: +- github: +- token: ${GITHUB_TOKEN} +- visibility: public # or 'internal' or 'private' ++ # see https://backstage.io/docs/features/software-templates/configuration for software template options + + catalog: ++ import: ++ entityFilename: catalog-info.yaml ++ pullRequestBranchName: backstage-integration + rules: +- - allow: [Component, System, API, Group, User, Resource, Location] ++ - allow: [Component, System, API, Resource, Location] + locations: +- # Backstage example components +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-components.yaml +- +- # Backstage example systems +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-systems.yaml +- +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml +- +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml ++ # Local example data, file locations are relative to the backend process, typically `packages/backend` ++ - type: file ++ target: ../../examples/entities.yaml + +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.yaml +- +- # Backstage example templates +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/react-ssr-template/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/springboot-grpc-template/template.yaml ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml + rules: + - allow: [Template] +- - type: url +- target: https://github.com/spotify/cookiecutter-golang/blob/master/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/docs-template/template.yaml ++ ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml + rules: +- - allow: [Template] ++ - allow: [User, Group] ++ ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml ++ ++ ## Uncomment these lines to add an example org ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme-corp.yaml ++ # rules: ++ # - allow: [User, Group] +diff --git a/backstage.json b/backstage.json +index 1062ff8..707af29 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "0.4.6" ++ "version": "1.20.0-next.1" + } +diff --git a/examples/entities.yaml b/examples/entities.yaml +new file mode 100644 +index 0000000..447e8b1 +--- /dev/null ++++ b/examples/entities.yaml +@@ -0,0 +1,41 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system ++apiVersion: backstage.io/v1alpha1 ++kind: System ++metadata: ++ name: examples ++spec: ++ owner: guests ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: example-website ++spec: ++ type: website ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ providesApis: [example-grpc-api] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api ++apiVersion: backstage.io/v1alpha1 ++kind: API ++metadata: ++ name: example-grpc-api ++spec: ++ type: grpc ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ definition: | ++ syntax = "proto3"; ++ ++ service Exampler { ++ rpc Example (ExampleMessage) returns (ExampleMessage) {}; ++ } ++ ++ message ExampleMessage { ++ string example = 1; ++ }; +diff --git a/examples/org.yaml b/examples/org.yaml +new file mode 100644 +index 0000000..a10e81f +--- /dev/null ++++ b/examples/org.yaml +@@ -0,0 +1,17 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user ++apiVersion: backstage.io/v1alpha1 ++kind: User ++metadata: ++ name: guest ++spec: ++ memberOf: [guests] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group ++apiVersion: backstage.io/v1alpha1 ++kind: Group ++metadata: ++ name: guests ++spec: ++ type: team ++ children: [] +diff --git a/examples/template/content/catalog-info.yaml b/examples/template/content/catalog-info.yaml +new file mode 100644 +index 0000000..d4ccca4 +--- /dev/null ++++ b/examples/template/content/catalog-info.yaml +@@ -0,0 +1,8 @@ ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: ${{ values.name | dump }} ++spec: ++ type: service ++ owner: user:guest ++ lifecycle: experimental +diff --git a/examples/template/content/index.js b/examples/template/content/index.js +new file mode 100644 +index 0000000..071ce5a +--- /dev/null ++++ b/examples/template/content/index.js +@@ -0,0 +1 @@ ++console.log('Hello from ${{ values.name }}!'); +diff --git a/examples/template/content/package.json b/examples/template/content/package.json +new file mode 100644 +index 0000000..86f968a +--- /dev/null ++++ b/examples/template/content/package.json +@@ -0,0 +1,5 @@ ++{ ++ "name": "${{ values.name }}", ++ "private": true, ++ "dependencies": {} ++} +diff --git a/examples/template/template.yaml b/examples/template/template.yaml +new file mode 100644 +index 0000000..33f262b +--- /dev/null ++++ b/examples/template/template.yaml +@@ -0,0 +1,74 @@ ++apiVersion: scaffolder.backstage.io/v1beta3 ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template ++kind: Template ++metadata: ++ name: example-nodejs-template ++ title: Example Node.js Template ++ description: An example template for the scaffolder that creates a simple Node.js service ++spec: ++ owner: user:guest ++ type: service ++ ++ # These parameters are used to generate the input form in the frontend, and are ++ # used to gather input data for the execution of the template. ++ parameters: ++ - title: Fill in some steps ++ required: ++ - name ++ properties: ++ name: ++ title: Name ++ type: string ++ description: Unique name of the component ++ ui:autofocus: true ++ ui:options: ++ rows: 5 ++ - title: Choose a location ++ required: ++ - repoUrl ++ properties: ++ repoUrl: ++ title: Repository Location ++ type: string ++ ui:field: RepoUrlPicker ++ ui:options: ++ allowedHosts: ++ - github.com ++ ++ # These steps are executed in the scaffolder backend, using data that we gathered ++ # via the parameters above. ++ steps: ++ # Each step executes an action, in this case one templates files into the working directory. ++ - id: fetch-base ++ name: Fetch Base ++ action: fetch:template ++ input: ++ url: ./content ++ values: ++ name: ${{ parameters.name }} ++ ++ # This step publishes the contents of the working directory to GitHub. ++ - id: publish ++ name: Publish ++ action: publish:github ++ input: ++ allowedHosts: ['github.com'] ++ description: This is ${{ parameters.name }} ++ repoUrl: ${{ parameters.repoUrl }} ++ ++ # The final step is to register our new component in the catalog. ++ - id: register ++ name: Register ++ action: catalog:register ++ input: ++ repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} ++ catalogInfoPath: '/catalog-info.yaml' ++ ++ # Outputs are displayed to the user after a successful execution of the template. ++ output: ++ links: ++ - title: Repository ++ url: ${{ steps['publish'].output.remoteUrl }} ++ - title: Open in catalog ++ icon: catalog ++ entityRef: ${{ steps['register'].output.entityRef }} +diff --git a/lerna.json b/lerna.json +index 322929d..529a62f 100644 +--- a/lerna.json ++++ b/lerna.json +@@ -3,4 +3,4 @@ + "npmClient": "yarn", +- "useWorkspaces": true, +- "version": "0.1.0" ++ "version": "0.1.0", ++ "$schema": "node_modules/lerna/schemas/lerna-schema.json" + } +diff --git a/package.json b/package.json +index 95de0a0..2364da8 100644 +--- a/package.json ++++ b/package.json +@@ -5,3 +5,3 @@ + "engines": { +- "node": "14 || 16" ++ "node": "18 || 20" + }, +@@ -11,3 +11,4 @@ + "start-backend": "yarn workspace backend start", +- "build": "lerna run build", ++ "build:backend": "yarn workspace backend build", ++ "build:all": "backstage-cli repo build --all", + "build-image": "yarn workspace backend build-image", +@@ -15,11 +16,11 @@ + "tsc:full": "tsc --skipLibCheck false --incremental false", +- "clean": "backstage-cli clean && lerna run clean", +- "diff": "lerna run diff --", +- "test": "backstage-cli test", +- "test:all": "lerna run test -- --coverage", +- "lint": "lerna run lint --since origin/master --", +- "lint:all": "lerna run lint --", ++ "clean": "backstage-cli repo clean", ++ "test": "backstage-cli repo test", ++ "test:all": "backstage-cli repo test --coverage", ++ "test:e2e": "playwright test", ++ "fix": "backstage-cli repo fix", ++ "lint": "backstage-cli repo lint --since origin/master", ++ "lint:all": "backstage-cli repo lint", + "prettier:check": "prettier --check .", +- "create-plugin": "backstage-cli create-plugin --scope internal", +- "remove-plugin": "backstage-cli remove-plugin" ++ "new": "backstage-cli new --scope internal" + }, +@@ -32,7 +33,15 @@ + "devDependencies": { +- "@backstage/cli": "^0.10.0", +- "@spotify/prettier-config": "^11.0.0", +- "concurrently": "^6.0.0", +- "lerna": "^4.0.0", +- "prettier": "^2.3.2" ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/e2e-test-utils": "^0.1.0", ++ "@playwright/test": "^1.32.3", ++ "@spotify/prettier-config": "^12.0.0", ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^9.0.0", ++ "prettier": "^2.3.2", ++ "typescript": "~5.2.0" ++ }, ++ "resolutions": { ++ "@types/react": "^17", ++ "@types/react-dom": "^17" + }, +@@ -40,3 +49,3 @@ + "lint-staged": { +- "*.{js,jsx,ts,tsx}": [ ++ "*.{js,jsx,ts,tsx,mjs,cjs}": [ + "eslint --fix", +diff --git a/packages/README.md b/packages/README.md +new file mode 100644 +index 0000000..6327fa0 +--- /dev/null ++++ b/packages/README.md +@@ -0,0 +1,9 @@ ++# The Packages Folder ++ ++This is where your own applications and centrally managed libraries live, each ++in a separate folder of its own. ++ ++From the start there's an `app` folder (for the frontend) and a `backend` folder ++(for the Node backend), but you can also add more modules in here that house ++your core additions and adaptations, such as themes, common React component ++libraries, utilities, and similar. +diff --git a/packages/app/.eslintignore b/packages/app/.eslintignore +new file mode 100644 +index 0000000..a48cf0d +--- /dev/null ++++ b/packages/app/.eslintignore +@@ -0,0 +1 @@ ++public +diff --git a/packages/app/.eslintrc.js b/packages/app/.eslintrc.js +index 13573ef..e2a53a6 100644 +--- a/packages/app/.eslintrc.js ++++ b/packages/app/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/app/cypress.json b/packages/app/cypress.json +deleted file mode 100644 +index 5de7ebf..0000000 +--- a/packages/app/cypress.json ++++ /dev/null +@@ -1,5 +0,0 @@ +-{ +- "baseUrl": "http://localhost:3001", +- "fixturesFolder": false, +- "pluginsFile": false +-} +diff --git a/packages/app/cypress/.eslintrc.json b/packages/app/cypress/.eslintrc.json +deleted file mode 100644 +index 2b3a458..0000000 +--- a/packages/app/cypress/.eslintrc.json ++++ /dev/null +@@ -1,21 +0,0 @@ +-{ +- "plugins": ["cypress"], +- "extends": ["plugin:cypress/recommended"], +- "rules": { +- "jest/expect-expect": [ +- "error", +- { +- "assertFunctionNames": ["expect", "cy.contains"] +- } +- ], +- "import/no-extraneous-dependencies": [ +- "error", +- { +- "devDependencies": true, +- "optionalDependencies": true, +- "peerDependencies": true, +- "bundledDependencies": true +- } +- ] +- } +-} +diff --git a/packages/app/cypress/integration/app.js b/packages/app/cypress/integration/app.js +deleted file mode 100644 +index 43fb2e3..0000000 +--- a/packages/app/cypress/integration/app.js ++++ /dev/null +@@ -1,6 +0,0 @@ +-describe('App', () => { +- it('should render the catalog', () => { +- cy.visit('/'); +- cy.contains('My Company Catalog'); +- }); +-}); +diff --git a/packages/app/e2e-tests/app.test.ts b/packages/app/e2e-tests/app.test.ts +new file mode 100644 +index 0000000..d45bc0d +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,23 @@ ++/* ++ * Copyright 2020 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { test, expect } from '@playwright/test'; ++ ++test('App should render the welcome page', async ({ page }) => { ++ await page.goto('/'); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 9f1021a..2100405 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,23 +5,38 @@ + "bundled": true, ++ "backstage": { ++ "role": "frontend" ++ }, ++ "scripts": { ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "clean": "backstage-cli package clean", ++ "test": "backstage-cli package test", ++ "lint": "backstage-cli package lint" ++ }, + "dependencies": { +- "@backstage/app-defaults": "^0.1.1", +- "@backstage/catalog-model": "^0.9.7", +- "@backstage/cli": "^0.10.0", +- "@backstage/core-app-api": "^0.1.24", +- "@backstage/core-components": "^0.7.6", +- "@backstage/core-plugin-api": "^0.2.2", +- "@backstage/integration-react": "^0.1.14", +- "@backstage/plugin-api-docs": "^0.6.16", +- "@backstage/plugin-catalog": "^0.7.3", +- "@backstage/plugin-catalog-import": "^0.7.4", +- "@backstage/plugin-catalog-react": "^0.6.4", +- "@backstage/plugin-github-actions": "^0.4.25", +- "@backstage/plugin-org": "^0.3.29", +- "@backstage/plugin-scaffolder": "^0.11.13", +- "@backstage/plugin-search": "^0.5.0", +- "@backstage/plugin-tech-radar": "^0.4.12", +- "@backstage/plugin-techdocs": "^0.12.8", +- "@backstage/plugin-user-settings": "^0.3.12", +- "@backstage/test-utils": "^0.1.23", +- "@backstage/theme": "^0.2.14", ++ "@backstage/app-defaults": "^1.4.5-next.1", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/core-app-api": "^1.11.1-next.0", ++ "@backstage/core-components": "^0.13.8-next.1", ++ "@backstage/core-plugin-api": "^1.8.0-next.0", ++ "@backstage/integration-react": "^1.1.21-next.1", ++ "@backstage/plugin-api-docs": "^0.9.13-next.1", ++ "@backstage/plugin-catalog": "^1.15.0-next.1", ++ "@backstage/plugin-catalog-common": "^1.0.17", ++ "@backstage/plugin-catalog-graph": "^0.2.38-next.1", ++ "@backstage/plugin-catalog-import": "^0.10.2-next.1", ++ "@backstage/plugin-catalog-react": "^1.9.0-next.1", ++ "@backstage/plugin-github-actions": "^0.6.7-next.1", ++ "@backstage/plugin-org": "^0.6.16-next.1", ++ "@backstage/plugin-permission-react": "^0.4.17-next.0", ++ "@backstage/plugin-scaffolder": "^1.16.0-next.1", ++ "@backstage/plugin-search": "^1.4.2-next.1", ++ "@backstage/plugin-search-react": "^1.7.2-next.1", ++ "@backstage/plugin-tech-radar": "^0.6.10-next.1", ++ "@backstage/plugin-techdocs": "^1.8.1-next.1", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.2-next.1", ++ "@backstage/plugin-techdocs-react": "^1.1.13-next.1", ++ "@backstage/plugin-user-settings": "^0.7.12-next.1", ++ "@backstage/theme": "^0.4.4-next.0", + "@material-ui/core": "^4.12.2", +@@ -29,30 +44,17 @@ + "history": "^5.0.0", +- "react": "^16.13.1", +- "react-dom": "^16.13.1", +- "react-router": "6.0.0-beta.0", +- "react-router-dom": "6.0.0-beta.0", +- "react-use": "^15.3.3" ++ "react": "^17.0.2", ++ "react-dom": "^17.0.2", ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { ++ "@backstage/test-utils": "^1.4.5-next.0", ++ "@playwright/test": "^1.32.3", + "@testing-library/jest-dom": "^5.10.1", +- "@testing-library/react": "^10.4.1", +- "@testing-library/user-event": "^12.0.7", +- "@types/jest": "^26.0.7", +- "@types/node": "^14.14.32", ++ "@testing-library/react": "^12.1.3", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^8.0.0", + "@types/react-dom": "*", +- "cross-env": "^7.0.0", +- "cypress": "^7.3.0", +- "eslint-plugin-cypress": "^2.10.3", +- "start-server-and-test": "^1.10.11" +- }, +- "scripts": { +- "start": "backstage-cli app:serve", +- "build": "backstage-cli app:build", +- "test": "backstage-cli test", +- "lint": "backstage-cli lint", +- "clean": "backstage-cli clean", +- "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", +- "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", +- "cy:dev": "cypress open", +- "cy:run": "cypress run" ++ "cross-env": "^7.0.0" + }, +diff --git a/packages/app/public/index.html b/packages/app/public/index.html +index b93896c..fc758ee 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -10,3 +10,2 @@ + /> +- + +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 8a53583..8d62f29 100644 +--- a/packages/app/src/App.tsx ++++ b/packages/app/src/App.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { Navigate, Route } from 'react-router'; ++import { Navigate, Route } from 'react-router-dom'; + import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; +@@ -13,2 +13,3 @@ import { + import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; ++import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +@@ -16,3 +17,2 @@ import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -21,2 +21,4 @@ import { + } from '@backstage/plugin-techdocs'; ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; + import { UserSettingsPage } from '@backstage/plugin-user-settings'; +@@ -29,3 +31,6 @@ import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; + import { createApp } from '@backstage/app-defaults'; +-import { FlatRoutes } from '@backstage/core-app-api'; ++import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; ++import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; ++import { RequirePermission } from '@backstage/plugin-permission-react'; ++import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; + +@@ -37,5 +42,6 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); + bind(apiDocsPlugin.externalRoutes, { +- createComponent: scaffolderPlugin.routes.root, ++ registerApi: catalogImportPlugin.routes.importPage, + }); +@@ -43,2 +49,6 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, ++ }); ++ bind(orgPlugin.externalRoutes, { ++ catalogIndex: catalogPlugin.routes.catalogIndex, + }); +@@ -47,8 +57,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -60,5 +67,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -73,3 +82,10 @@ const routes = ( + /> +- } /> ++ ++ ++ ++ } ++ /> + }> +@@ -78,2 +94,3 @@ const routes = ( + } /> ++ } /> + +@@ -81,4 +98,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -88,5 +105,3 @@ const App = () => ( + +- ++ , + ); +- +-export default App; +diff --git a/packages/app/src/components/Root/LogoFull.tsx b/packages/app/src/components/Root/LogoFull.tsx +index c7b1c84..47e3b73 100644 +--- a/packages/app/src/components/Root/LogoFull.tsx ++++ b/packages/app/src/components/Root/LogoFull.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/LogoIcon.tsx b/packages/app/src/components/Root/LogoIcon.tsx +index 073cf6e..7eae8c7 100644 +--- a/packages/app/src/components/Root/LogoIcon.tsx ++++ b/packages/app/src/components/Root/LogoIcon.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx +index b4fa04f..6768b48 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,19 +1,3 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- +-import React, { useContext, PropsWithChildren } from 'react'; +-import { Link, makeStyles } from '@material-ui/core'; ++import React, { PropsWithChildren } from 'react'; ++import { makeStyles } from '@material-ui/core'; + import HomeIcon from '@material-ui/icons/Home'; +@@ -25,18 +9,21 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; +-import { Settings as SidebarSettings } from '@backstage/plugin-user-settings'; + import { +- SidebarSearchModal, +- SearchContextProvider, +-} from '@backstage/plugin-search'; ++ Settings as SidebarSettings, ++ UserSettingsSignInAvatar, ++} from '@backstage/plugin-user-settings'; ++import { SidebarSearchModal } from '@backstage/plugin-search'; + import { + Sidebar, +- SidebarPage, + sidebarConfig, +- SidebarContext, +- SidebarItem, + SidebarDivider, +- SidebarSpace, ++ SidebarGroup, ++ SidebarItem, ++ SidebarPage, + SidebarScrollWrapper, ++ SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; ++import MenuIcon from '@material-ui/icons/Menu'; ++import SearchIcon from '@material-ui/icons/Search'; + +@@ -59,3 +46,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -63,8 +50,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -79,19 +61,27 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ } to="/search"> + +- ++ + +- {/* Global nav, not org-specific */} +- +- +- +- +- {/* End global nav */} +- +- +- +- ++ }> ++ {/* Global nav, not org-specific */} ++ ++ ++ ++ ++ {/* End global nav */} ++ ++ ++ ++ ++ + + +- ++ } ++ to="/settings" ++ > ++ ++ + +diff --git a/packages/app/src/components/Root/index.ts b/packages/app/src/components/Root/index.ts +index dff706f..3528881 100644 +--- a/packages/app/src/components/Root/index.ts ++++ b/packages/app/src/components/Root/index.ts +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + export { Root } from './Root'; +diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx +index d3b4b78..6722ea2 100644 +--- a/packages/app/src/components/catalog/EntityPage.tsx ++++ b/packages/app/src/components/catalog/EntityPage.tsx +@@ -1,16 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ + import React from 'react'; +@@ -29,3 +14,2 @@ import { + EntityDependsOnResourcesCard, +- EntitySystemDiagramCard, + EntityHasComponentsCard, +@@ -43,2 +27,4 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +@@ -56,2 +42,27 @@ import { EntityTechdocsContent } from '@backstage/plugin-techdocs'; + import { EmptyState } from '@backstage/core-components'; ++import { ++ Direction, ++ EntityCatalogGraphCard, ++} from '@backstage/plugin-catalog-graph'; ++import { ++ RELATION_API_CONSUMED_BY, ++ RELATION_API_PROVIDED_BY, ++ RELATION_CONSUMES_API, ++ RELATION_DEPENDENCY_OF, ++ RELATION_DEPENDS_ON, ++ RELATION_HAS_PART, ++ RELATION_PART_OF, ++ RELATION_PROVIDES_API, ++} from '@backstage/catalog-model'; ++ ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); + +@@ -94,2 +105,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -110,2 +129,6 @@ const overviewContent = ( + ++ ++ ++ ++ + +@@ -152,3 +175,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -179,3 +202,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -198,3 +221,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -225,2 +248,5 @@ const apiPage = ( + ++ ++ ++ + +@@ -292,3 +318,9 @@ const systemPage = ( + +- ++ ++ ++ ++ ++ ++ ++ + +@@ -304,3 +336,19 @@ const systemPage = ( + +- ++ + +@@ -317,2 +365,5 @@ const domainPage = ( + ++ ++ ++ + +diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx +index 50ffbad..1788dde 100644 +--- a/packages/app/src/components/search/SearchPage.tsx ++++ b/packages/app/src/components/search/SearchPage.tsx +@@ -1,7 +1,12 @@ + import React from 'react'; +-import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; ++import { makeStyles, Theme, Grid, Paper } from '@material-ui/core'; + +-import { CatalogResultListItem } from '@backstage/plugin-catalog'; +-import { DocsResultListItem } from '@backstage/plugin-techdocs'; ++import { CatalogSearchResultListItem } from '@backstage/plugin-catalog'; ++import { ++ catalogApiRef, ++ CATALOG_FILTER_EXISTS, ++} from '@backstage/plugin-catalog-react'; ++import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; + ++import { SearchType } from '@backstage/plugin-search'; + import { +@@ -10,6 +15,13 @@ import { + SearchResult, +- SearchType, +- DefaultResultListItem, +-} from '@backstage/plugin-search'; +-import { Content, Header, Page } from '@backstage/core-components'; ++ SearchPagination, ++ useSearch, ++} from '@backstage/plugin-search-react'; ++import { ++ CatalogIcon, ++ Content, ++ DocsIcon, ++ Header, ++ Page, ++} from '@backstage/core-components'; ++import { useApi } from '@backstage/core-plugin-api'; + +@@ -21,2 +33,3 @@ const useStyles = makeStyles((theme: Theme) => ({ + padding: theme.spacing(2), ++ marginTop: theme.spacing(2), + }, +@@ -31,2 +44,4 @@ const SearchPage = () => { + const classes = useStyles(); ++ const { types } = useSearch(); ++ const catalogApi = useApi(catalogApiRef); + +@@ -39,3 +54,3 @@ const SearchPage = () => { + +- ++ + +@@ -43,10 +58,43 @@ const SearchPage = () => { + ++ , ++ }, ++ { ++ value: 'techdocs', ++ name: 'Documentation', ++ icon: , ++ }, ++ ]} ++ /> + +- ++ {types.includes('techdocs') && ( ++ { ++ // Return a list of entities which are documented. ++ const { items } = await catalogApi.getEntities({ ++ fields: ['metadata.name'], ++ filter: { ++ 'metadata.annotations.backstage.io/techdocs-ref': ++ CATALOG_FILTER_EXISTS, ++ }, ++ }); ++ ++ const names = items.map(entity => entity.metadata.name); ++ names.sort(); ++ return names; ++ }} ++ /> ++ )} + { + className={classes.filter} ++ label="Lifecycle" + name="lifecycle" +@@ -62,32 +111,6 @@ const SearchPage = () => { + ++ + +- {({ results }) => ( +- +- {results.map(({ type, document }) => { +- switch (type) { +- case 'software-catalog': +- return ( +- +- ); +- case 'techdocs': +- return ( +- +- ); +- default: +- return ( +- +- ); +- } +- })} +- +- )} ++ } /> ++ } /> + +diff --git a/packages/backend/.eslintrc.js b/packages/backend/.eslintrc.js +index 16a033d..e2a53a6 100644 +--- a/packages/backend/.eslintrc.js ++++ b/packages/backend/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint.backend')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index 31231a3..18548e9 100644 +--- a/packages/backend/Dockerfile ++++ b/packages/backend/Dockerfile +@@ -7,3 +7,3 @@ + # yarn tsc +-# yarn build ++# yarn build:backend + # +@@ -11,6 +11,29 @@ + +-FROM node:14-buster-slim ++FROM node:18-bookworm-slim + ++# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends python3 g++ build-essential && \ ++ yarn config set python /usr/bin/python3 ++ ++# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, ++# in which case you should also move better-sqlite3 to "devDependencies" in package.json. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends libsqlite3-dev ++ ++# From here on we use the least-privileged `node` user to run the backend. ++USER node ++ ++# This should create the app dir as `node`. ++# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. ++# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. + WORKDIR /app + ++# This switches many Node.js dependencies to production mode. ++ENV NODE_ENV production ++ + # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. +@@ -18,11 +41,12 @@ WORKDIR /app + # and along with yarn.lock and the root package.json, that's enough to run yarn install. +-COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ ++COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ + RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz + +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + + # Then copy the rest of the backend bundle, along with any other files we might want. +-COPY packages/backend/dist/bundle.tar.gz app-config.yaml ./ ++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"] +diff --git a/packages/backend/README.md b/packages/backend/README.md +index 02426ef..3607b0a 100644 +--- a/packages/backend/README.md ++++ b/packages/backend/README.md +@@ -18,4 +18,2 @@ To run the example backend, first go to the project root and run + yarn install +-yarn tsc +-yarn build + ``` +@@ -27,7 +25,2 @@ After that, go to the `packages/backend` directory and run + ```bash +-AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x \ +-AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x \ +-AUTH_OAUTH2_CLIENT_ID=x AUTH_OAUTH2_CLIENT_SECRET=x \ +-AUTH_OAUTH2_AUTH_URL=x AUTH_OAUTH2_TOKEN_URL=x \ +-LOG_LEVEL=debug \ + yarn start +@@ -35,4 +28,4 @@ yarn start + +-Substitute `x` for actual values, or leave them as dummy values just to try out +-the backend without using the auth or sentry features. ++If you want to override any configuration locally, for example adding any secrets, ++you can do so in `app-config.local.yaml`. + +@@ -45,3 +38,3 @@ locations to the backend. These are places where the backend can find some + entity descriptor data to consume and serve. For more information, see +-[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog). ++[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). + +@@ -65,2 +58,2 @@ and + - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +-- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) ++- [Backstage Documentation](https://backstage.io/docs) +diff --git a/packages/backend/package.json b/packages/backend/package.json +index e908e1e..f224eaf 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,27 +6,33 @@ + "private": true, ++ "backstage": { ++ "role": "backend" ++ }, + "scripts": { +- "build": "backstage-cli backend:bundle", +- "build-image": "docker build ../.. -f Dockerfile --tag backstage", +- "start": "backstage-cli backend:dev", +- "lint": "backstage-cli lint", +- "test": "backstage-cli test", +- "clean": "backstage-cli clean", +- "migrate:create": "knex migrate:make -x ts" ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "lint": "backstage-cli package lint", ++ "test": "backstage-cli package test", ++ "clean": "backstage-cli package clean", ++ "build-image": "docker build ../.. -f Dockerfile --tag backstage" + }, + "dependencies": { +- "app": "0.0.0", +- "@backstage/backend-common": "^0.9.12", +- "@backstage/catalog-model": "^0.9.7", +- "@backstage/catalog-client": "^0.5.2", +- "@backstage/config": "^0.1.11", +- "@backstage/plugin-app-backend": "^0.3.19", +- "@backstage/plugin-auth-backend": "^0.4.10", +- "@backstage/plugin-catalog-backend": "^0.19.0", +- "@backstage/plugin-proxy-backend": "^0.2.14", +- "@backstage/plugin-scaffolder-backend": "^0.15.15", +- "@backstage/plugin-search-backend": "^0.2.7", +- "@backstage/plugin-search-backend-node": "^0.4.3", +- "@backstage/plugin-techdocs-backend": "^0.11.0", +- "@gitbeaker/node": "^34.6.0", +- "@octokit/rest": "^18.5.3", ++ "@backstage/backend-common": "^0.19.9-next.1", ++ "@backstage/backend-tasks": "^0.5.12-next.1", ++ "@backstage/catalog-client": "^1.4.5", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/config": "^1.1.1", ++ "@backstage/plugin-app-backend": "^0.3.55-next.1", ++ "@backstage/plugin-auth-backend": "^0.20.0-next.1", ++ "@backstage/plugin-auth-node": "^0.4.1-next.1", ++ "@backstage/plugin-catalog-backend": "^1.15.0-next.1", ++ "@backstage/plugin-permission-common": "^0.7.9", ++ "@backstage/plugin-permission-node": "^0.7.18-next.1", ++ "@backstage/plugin-proxy-backend": "^0.4.5-next.1", ++ "@backstage/plugin-scaffolder-backend": "^1.19.0-next.1", ++ "@backstage/plugin-search-backend": "^1.4.7-next.1", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.16-next.1", ++ "@backstage/plugin-search-backend-node": "^1.2.11-next.1", ++ "@backstage/plugin-techdocs-backend": "^1.9.0-next.1", ++ "app": "link:../app", ++ "better-sqlite3": "^8.0.0", + "dockerode": "^3.3.1", +@@ -34,4 +40,3 @@ + "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "pg": "^8.3.0", + "winston": "^3.2.1" +@@ -39,6 +44,7 @@ + "devDependencies": { +- "@backstage/cli": "^0.10.0", ++ "@backstage/cli": "^0.24.0-next.1", + "@types/dockerode": "^3.3.0", + "@types/express": "^4.17.6", +- "@types/express-serve-static-core": "^4.17.5" ++ "@types/express-serve-static-core": "^4.17.5", ++ "@types/luxon": "^2.0.4" + }, +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 3f12122..04c4ff9 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -17,3 +17,3 @@ import { + DatabaseManager, +- SingleHostDiscovery, ++ HostDiscovery, + UrlReaders, +@@ -21,2 +21,3 @@ import { + } from '@backstage/backend-common'; ++import { TaskScheduler } from '@backstage/backend-tasks'; + import { Config } from '@backstage/config'; +@@ -30,2 +31,4 @@ import search from './plugins/search'; + import { PluginEnvironment } from './types'; ++import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; + +@@ -34,9 +37,17 @@ function makeCreateEnv(config: Config) { + const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); +- +- root.info(`Created UrlReader ${reader}`); +- ++ const discovery = HostDiscovery.fromConfig(config); + const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++ const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); + const tokenManager = ServerTokenManager.noop(); ++ const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); ++ ++ const identity = DefaultIdentityClient.create({ ++ discovery, ++ }); ++ const permissions = ServerPermissionClient.fromConfig(config, { ++ discovery, ++ tokenManager, ++ }); ++ ++ root.info(`Created UrlReader ${reader}`); + +@@ -46,3 +57,15 @@ function makeCreateEnv(config: Config) { + const cache = cacheManager.forPlugin(plugin); +- return { logger, database, cache, config, reader, discovery, tokenManager }; ++ const scheduler = taskScheduler.forPlugin(plugin); ++ return { ++ logger, ++ database, ++ cache, ++ config, ++ reader, ++ discovery, ++ tokenManager, ++ scheduler, ++ permissions, ++ identity, ++ }; + }; +@@ -72,2 +95,4 @@ async function main() { + apiRouter.use('/search', await search(searchEnv)); ++ ++ // Add backends ABOVE this line; this 404 handler is the catch-all fallback + apiRouter.use(notFoundHandler()); +@@ -87,3 +112,3 @@ module.hot?.accept(); + main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); ++ console.error('Backend failed to start up', error); + process.exit(1); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +index 07fb04f..7c37f68 100644 +--- a/packages/backend/src/plugins/app.ts ++++ b/packages/backend/src/plugins/app.ts +@@ -4,9 +4,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + return await createRouter({ +- logger, +- config, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, + appPackageName: 'app', +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +index 5216510..77eb6aa 100644 +--- a/packages/backend/src/plugins/auth.ts ++++ b/packages/backend/src/plugins/auth.ts +@@ -1,2 +1,6 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; ++import { ++ createRouter, ++ providers, ++ defaultAuthProviderFactories, ++} from '@backstage/plugin-auth-backend'; + import { Router } from 'express'; +@@ -4,9 +8,47 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, database, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, ++ providerFactories: { ++ ...defaultAuthProviderFactories, ++ ++ // This replaces the default GitHub auth provider with a customized one. ++ // The `signIn` option enables sign-in for this provider, using the ++ // identity resolution logic that's provided in the `resolver` callback. ++ // ++ // This particular resolver makes all users share a single "guest" identity. ++ // It should only be used for testing and trying out Backstage. ++ // ++ // If you want to use a production ready resolver you can switch to ++ // the one that is commented out below, it looks up a user entity in the ++ // catalog using the GitHub username of the authenticated user. ++ // That resolver requires you to have user entities populated in the catalog, ++ // for example using https://backstage.io/docs/integrations/github/org ++ // ++ // There are other resolvers to choose from, and you can also create ++ // your own, see the auth documentation for more details: ++ // ++ // https://backstage.io/docs/auth/identity-resolver ++ github: providers.github.create({ ++ signIn: { ++ resolver(_, ctx) { ++ const userRef = 'user:default/guest'; // Must be a full entity reference ++ return ctx.issueToken({ ++ claims: { ++ sub: userRef, // The user's own identity ++ ent: [userRef], // A list of identities that the user claims ownership through ++ }, ++ }); ++ }, ++ // resolver: providers.github.resolvers.usernameMatchingUserEntityName(), ++ }, ++ }), ++ }, ++ }); + } +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +index 506f6d9..54ec393 100644 +--- a/packages/backend/src/plugins/proxy.ts ++++ b/packages/backend/src/plugins/proxy.ts +@@ -4,8 +4,10 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ }); + } +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +index 6be2e97..a12fee2 100644 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ b/packages/backend/src/plugins/scaffolder.ts +@@ -1,5 +1,3 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; + import { CatalogClient } from '@backstage/catalog-client'; + import { createRouter } from '@backstage/plugin-scaffolder-backend'; +-import Docker from 'dockerode'; + import { Router } from 'express'; +@@ -7,20 +5,17 @@ import type { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- database, +- reader, +- discovery, +-}: PluginEnvironment): Promise { +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- const catalogClient = new CatalogClient({ discoveryApi: discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ const catalogClient = new CatalogClient({ ++ discoveryApi: env.discovery, ++ }); + + return await createRouter({ +- containerRunner, +- logger, +- config, +- database, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ reader: env.reader, + catalogClient, +- reader, ++ identity: env.identity, ++ permissions: env.permissions, + }); +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +index f23b0c7..e9469dc 100644 +--- a/packages/backend/src/plugins/search.ts ++++ b/packages/backend/src/plugins/search.ts +@@ -7,14 +7,25 @@ import { + import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend'; ++import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend'; ++import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; ++import { Router } from 'express'; + +-export default async function createPlugin({ +- logger, +- discovery, +- config, +- tokenManager, +-}: PluginEnvironment) { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); ++ const searchEngine = new LunrSearchEngine({ ++ logger: env.logger, ++ }); ++ const indexBuilder = new IndexBuilder({ ++ logger: env.logger, ++ searchEngine, ++ }); ++ ++ const schedule = env.scheduler.createScheduledTaskRunner({ ++ frequency: { minutes: 10 }, ++ timeout: { minutes: 15 }, ++ // A 3 second delay gives the backend server a chance to initialize before ++ // any collators are executed, which may attempt requests against the API. ++ initialDelay: { seconds: 3 }, ++ }); + +@@ -23,6 +34,6 @@ export default async function createPlugin({ + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { +- discovery, +- tokenManager, ++ schedule, ++ factory: DefaultCatalogCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, + }), +@@ -32,7 +43,7 @@ export default async function createPlugin({ + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultTechDocsCollator.fromConfig(config, { +- discovery, +- logger, +- tokenManager, ++ schedule, ++ factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ logger: env.logger, ++ tokenManager: env.tokenManager, + }), +@@ -43,6 +54,4 @@ export default async function createPlugin({ + const { scheduler } = await indexBuilder.build(); ++ scheduler.start(); + +- // A 3 second delay gives the backend server a chance to initialize before +- // any collators are executed, which may attempt requests against the API. +- setTimeout(() => scheduler.start(), 3000); + useHotCleanup(module, () => scheduler.stop()); +@@ -51,3 +60,6 @@ export default async function createPlugin({ + engine: indexBuilder.getSearchEngine(), +- logger, ++ types: indexBuilder.getDocumentTypes(), ++ permissions: env.permissions, ++ config: env.config, ++ logger: env.logger, + }); +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +index 906d86d..be8bb0c 100644 +--- a/packages/backend/src/plugins/techdocs.ts ++++ b/packages/backend/src/plugins/techdocs.ts +@@ -11,12 +11,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, ++ const preparers = await Preparers.fromConfig(env.config, { ++ logger: env.logger, ++ reader: env.reader, + }); +@@ -28,4 +25,4 @@ export default async function createPlugin({ + // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, ++ const generators = await Generators.fromConfig(env.config, { ++ logger: env.logger, + containerRunner, +@@ -36,5 +33,5 @@ export default async function createPlugin({ + // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, ++ const publisher = await Publisher.fromConfig(env.config, { ++ logger: env.logger, ++ discovery: env.discovery, + }); +@@ -48,5 +45,6 @@ export default async function createPlugin({ + publisher, +- logger, +- config, +- discovery, ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ cache: env.cache, + }); +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +index b1e2e0a..9cd2c74 100644 +--- a/packages/backend/src/types.ts ++++ b/packages/backend/src/types.ts +@@ -9,2 +9,5 @@ import { + } from '@backstage/backend-common'; ++import { PluginTaskScheduler } from '@backstage/backend-tasks'; ++import { PermissionEvaluator } from '@backstage/plugin-permission-common'; ++import { IdentityApi } from '@backstage/plugin-auth-node'; + +@@ -18,2 +21,5 @@ export type PluginEnvironment = { + tokenManager: TokenManager; ++ scheduler: PluginTaskScheduler; ++ permissions: PermissionEvaluator; ++ identity: IdentityApi; + }; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..37c7fb1 +--- /dev/null ++++ b/playwright.config.ts +@@ -0,0 +1,60 @@ ++/* ++ * Copyright 2023 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { defineConfig } from '@playwright/test'; ++import { generateProjects } from '@backstage/e2e-test-utils/playwright'; ++ ++/** ++ * See https://playwright.dev/docs/test-configuration. ++ */ ++export default defineConfig({ ++ timeout: 60_000, ++ ++ expect: { ++ timeout: 5_000, ++ }, ++ ++ // Run your local dev server before starting the tests ++ webServer: process.env.CI ++ ? [] ++ : [ ++ { ++ command: 'yarn start', ++ port: 3000, ++ reuseExistingServer: true, ++ timeout: 60_000, ++ }, ++ ], ++ ++ forbidOnly: !!process.env.CI, ++ ++ retries: process.env.CI ? 2 : 0, ++ ++ reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]], ++ ++ use: { ++ actionTimeout: 0, ++ baseURL: ++ process.env.PLAYWRIGHT_URL ?? ++ (process.env.CI ? 'http://localhost:7007' : 'http://localhost:3000'), ++ screenshot: 'only-on-failure', ++ trace: 'on-first-retry', ++ }, ++ ++ outputDir: 'node_modules/.cache/e2e-test-results', ++ ++ projects: generateProjects(), // Find all packages with e2e-test folders ++}); +diff --git a/plugins/README.md b/plugins/README.md +new file mode 100644 +index 0000000..d7865fd +--- /dev/null ++++ b/plugins/README.md +@@ -0,0 +1,9 @@ ++# The Plugins Folder ++ ++This is where your own plugins and their associated modules live, each in a ++separate folder of its own. ++ ++If you want to create a new plugin here, go to your project root directory, run ++the command `yarn new`, and follow the on-screen instructions. ++ ++You can also check out existing plugins on [the plugin marketplace](https://backstage.io/plugins)! +diff --git a/yarn.lock b/yarn.lock +new file mode 100644 +index 0000000..5ad7fe2 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1 @@ ++# intentionally left empty diff --git a/diffs/0.4.7..0.5.7-next.1.diff b/diffs/0.4.7..0.5.7-next.1.diff new file mode 100644 index 00000000..d0a722c2 --- /dev/null +++ b/diffs/0.4.7..0.5.7-next.1.diff @@ -0,0 +1,1908 @@ +diff --git a/.dockerignore b/.dockerignore +index 63c9c34..05edb62 100644 +--- a/.dockerignore ++++ b/.dockerignore +@@ -1,5 +1,8 @@ + .git ++.yarn/cache ++.yarn/install-state.gz + node_modules +-packages +-!packages/backend/dist ++packages/*/src ++packages/*/node_modules + plugins ++*.local.yaml +diff --git a/.eslintignore b/.eslintignore +new file mode 100644 +index 0000000..e5b1994 +--- /dev/null ++++ b/.eslintignore +@@ -0,0 +1 @@ ++playwright.config.ts +diff --git a/.gitignore b/.gitignore +index d16a8d3..fbf8139 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -17,2 +17,11 @@ node_modules/ + ++# Yarn 3 files ++.pnp.* ++.yarn/* ++!.yarn/patches ++!.yarn/plugins ++!.yarn/releases ++!.yarn/sdks ++!.yarn/versions ++ + # Node version directives +@@ -39 +48,7 @@ site + *-credentials.yaml ++ ++# vscode database functionality support files ++*.session.sql ++ ++# E2E test reports ++e2e-test-report/ +diff --git a/app-config.production.yaml b/app-config.production.yaml +index 5e36c23..8f0751c 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -1,3 +1,3 @@ + app: +- # Should be the same as backend.baseUrl when using the `app-backend` plugin ++ # Should be the same as backend.baseUrl when using the `app-backend` plugin. + baseUrl: http://localhost:7007 +@@ -5,4 +5,31 @@ app: + backend: ++ # Note that the baseUrl should be the URL that the browser and other clients ++ # should use when communicating with the backend, i.e. it needs to be ++ # reachable not just from within the backend host, but from all of your ++ # callers. When its value is "http://localhost:7007", it's strictly private ++ # and can't be reached by others. + baseUrl: http://localhost:7007 +- listen: +- port: 7007 ++ # The listener can also be expressed as a single : string. In this case we bind to ++ # all interfaces, the most permissive setting. The right value depends on your specific deployment. ++ listen: ':7007' ++ ++ # config options: https://node-postgres.com/api/client ++ database: ++ client: pg ++ connection: ++ host: ${POSTGRES_HOST} ++ port: ${POSTGRES_PORT} ++ user: ${POSTGRES_USER} ++ password: ${POSTGRES_PASSWORD} ++ # https://node-postgres.com/features/ssl ++ # you can set the sslmode configuration option via the `PGSSLMODE` environment variable ++ # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require) ++ # ssl: ++ # ca: # if you have a CA file and want to verify it you can uncomment this section ++ # $file: /ca/server.crt ++ ++catalog: ++ # Overrides the default list locations from app-config.yaml as these contain example data. ++ # See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details ++ # on how to get entities into the catalog. ++ locations: [] +diff --git a/app-config.yaml b/app-config.yaml +index 1a622a2..08d2abf 100644 +--- a/app-config.yaml ++++ b/app-config.yaml +@@ -9,3 +9,4 @@ backend: + # Used for enabling authentication, secret is shared by all backend plugins +- # See backend-to-backend-auth.md in the docs for information on the format ++ # See https://backstage.io/docs/auth/service-to-service-auth for ++ # information on the format + # auth: +@@ -16,2 +17,4 @@ backend: + port: 7007 ++ # Uncomment the following host directive to bind to specific interfaces ++ # host: 127.0.0.1 + csp: +@@ -22,9 +25,9 @@ backend: + origin: http://localhost:3000 +- methods: [GET, POST, PUT, DELETE] ++ methods: [GET, HEAD, PATCH, POST, PUT, DELETE] + credentials: true ++ # This is for local development only, it is not recommended to use this in production ++ # The production database configuration is stored in app-config.production.yaml + database: +- client: sqlite3 ++ client: better-sqlite3 + connection: ':memory:' +- cache: +- store: memory + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir +@@ -34,2 +37,4 @@ integrations: + - host: github.com ++ # This is a Personal Access Token or PAT from GitHub. You can find out how to generate this token, and more information ++ # about setting up the GitHub integration here: https://backstage.io/docs/getting-started/configuration#setting-up-a-github-integration + token: ${GITHUB_TOKEN} +@@ -41,5 +46,8 @@ integrations: + proxy: +- '/test': +- target: 'https://example.com' +- changeOrigin: true ++ ### Example for how to add a proxy endpoint for the frontend. ++ ### A typical reason to do this is to handle HTTPS and CORS for internal services. ++ # endpoints: ++ # '/test': ++ # target: 'https://example.com' ++ # changeOrigin: true + +@@ -64,41 +72,32 @@ scaffolder: + catalog: ++ import: ++ entityFilename: catalog-info.yaml ++ pullRequestBranchName: backstage-integration + rules: +- - allow: [Component, System, API, Group, User, Resource, Location] ++ - allow: [Component, System, API, Resource, Location] + locations: +- # Backstage example components +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-components.yaml ++ # Local example data, file locations are relative to the backend process, typically `packages/backend` ++ - type: file ++ target: ../../examples/entities.yaml + +- # Backstage example systems +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-systems.yaml +- +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml +- +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml +- +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.yaml +- +- # Backstage example templates +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/react-ssr-template/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/springboot-grpc-template/template.yaml ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml + rules: + - allow: [Template] +- - type: url +- target: https://github.com/spotify/cookiecutter-golang/blob/master/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/docs-template/template.yaml ++ ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml + rules: +- - allow: [Template] ++ - allow: [User, Group] ++ ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml ++ ++ ## Uncomment these lines to add an example org ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme-corp.yaml ++ # rules: ++ # - allow: [User, Group] +diff --git a/backstage.json b/backstage.json +index 10f65a1..707af29 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "0.4.7" ++ "version": "1.20.0-next.1" + } +diff --git a/examples/entities.yaml b/examples/entities.yaml +new file mode 100644 +index 0000000..447e8b1 +--- /dev/null ++++ b/examples/entities.yaml +@@ -0,0 +1,41 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system ++apiVersion: backstage.io/v1alpha1 ++kind: System ++metadata: ++ name: examples ++spec: ++ owner: guests ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: example-website ++spec: ++ type: website ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ providesApis: [example-grpc-api] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api ++apiVersion: backstage.io/v1alpha1 ++kind: API ++metadata: ++ name: example-grpc-api ++spec: ++ type: grpc ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ definition: | ++ syntax = "proto3"; ++ ++ service Exampler { ++ rpc Example (ExampleMessage) returns (ExampleMessage) {}; ++ } ++ ++ message ExampleMessage { ++ string example = 1; ++ }; +diff --git a/examples/org.yaml b/examples/org.yaml +new file mode 100644 +index 0000000..a10e81f +--- /dev/null ++++ b/examples/org.yaml +@@ -0,0 +1,17 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user ++apiVersion: backstage.io/v1alpha1 ++kind: User ++metadata: ++ name: guest ++spec: ++ memberOf: [guests] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group ++apiVersion: backstage.io/v1alpha1 ++kind: Group ++metadata: ++ name: guests ++spec: ++ type: team ++ children: [] +diff --git a/examples/template/content/catalog-info.yaml b/examples/template/content/catalog-info.yaml +new file mode 100644 +index 0000000..d4ccca4 +--- /dev/null ++++ b/examples/template/content/catalog-info.yaml +@@ -0,0 +1,8 @@ ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: ${{ values.name | dump }} ++spec: ++ type: service ++ owner: user:guest ++ lifecycle: experimental +diff --git a/examples/template/content/index.js b/examples/template/content/index.js +new file mode 100644 +index 0000000..071ce5a +--- /dev/null ++++ b/examples/template/content/index.js +@@ -0,0 +1 @@ ++console.log('Hello from ${{ values.name }}!'); +diff --git a/examples/template/content/package.json b/examples/template/content/package.json +new file mode 100644 +index 0000000..86f968a +--- /dev/null ++++ b/examples/template/content/package.json +@@ -0,0 +1,5 @@ ++{ ++ "name": "${{ values.name }}", ++ "private": true, ++ "dependencies": {} ++} +diff --git a/examples/template/template.yaml b/examples/template/template.yaml +new file mode 100644 +index 0000000..33f262b +--- /dev/null ++++ b/examples/template/template.yaml +@@ -0,0 +1,74 @@ ++apiVersion: scaffolder.backstage.io/v1beta3 ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template ++kind: Template ++metadata: ++ name: example-nodejs-template ++ title: Example Node.js Template ++ description: An example template for the scaffolder that creates a simple Node.js service ++spec: ++ owner: user:guest ++ type: service ++ ++ # These parameters are used to generate the input form in the frontend, and are ++ # used to gather input data for the execution of the template. ++ parameters: ++ - title: Fill in some steps ++ required: ++ - name ++ properties: ++ name: ++ title: Name ++ type: string ++ description: Unique name of the component ++ ui:autofocus: true ++ ui:options: ++ rows: 5 ++ - title: Choose a location ++ required: ++ - repoUrl ++ properties: ++ repoUrl: ++ title: Repository Location ++ type: string ++ ui:field: RepoUrlPicker ++ ui:options: ++ allowedHosts: ++ - github.com ++ ++ # These steps are executed in the scaffolder backend, using data that we gathered ++ # via the parameters above. ++ steps: ++ # Each step executes an action, in this case one templates files into the working directory. ++ - id: fetch-base ++ name: Fetch Base ++ action: fetch:template ++ input: ++ url: ./content ++ values: ++ name: ${{ parameters.name }} ++ ++ # This step publishes the contents of the working directory to GitHub. ++ - id: publish ++ name: Publish ++ action: publish:github ++ input: ++ allowedHosts: ['github.com'] ++ description: This is ${{ parameters.name }} ++ repoUrl: ${{ parameters.repoUrl }} ++ ++ # The final step is to register our new component in the catalog. ++ - id: register ++ name: Register ++ action: catalog:register ++ input: ++ repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} ++ catalogInfoPath: '/catalog-info.yaml' ++ ++ # Outputs are displayed to the user after a successful execution of the template. ++ output: ++ links: ++ - title: Repository ++ url: ${{ steps['publish'].output.remoteUrl }} ++ - title: Open in catalog ++ icon: catalog ++ entityRef: ${{ steps['register'].output.entityRef }} +diff --git a/lerna.json b/lerna.json +index 322929d..529a62f 100644 +--- a/lerna.json ++++ b/lerna.json +@@ -3,4 +3,4 @@ + "npmClient": "yarn", +- "useWorkspaces": true, +- "version": "0.1.0" ++ "version": "0.1.0", ++ "$schema": "node_modules/lerna/schemas/lerna-schema.json" + } +diff --git a/package.json b/package.json +index cdf36ff..2364da8 100644 +--- a/package.json ++++ b/package.json +@@ -5,3 +5,3 @@ + "engines": { +- "node": "14 || 16" ++ "node": "18 || 20" + }, +@@ -11,3 +11,4 @@ + "start-backend": "yarn workspace backend start", +- "build": "lerna run build", ++ "build:backend": "yarn workspace backend build", ++ "build:all": "backstage-cli repo build --all", + "build-image": "yarn workspace backend build-image", +@@ -15,11 +16,11 @@ + "tsc:full": "tsc --skipLibCheck false --incremental false", +- "clean": "backstage-cli clean && lerna run clean", +- "diff": "lerna run diff --", +- "test": "backstage-cli test", +- "test:all": "lerna run test -- --coverage", +- "lint": "lerna run lint --since origin/master --", +- "lint:all": "lerna run lint --", ++ "clean": "backstage-cli repo clean", ++ "test": "backstage-cli repo test", ++ "test:all": "backstage-cli repo test --coverage", ++ "test:e2e": "playwright test", ++ "fix": "backstage-cli repo fix", ++ "lint": "backstage-cli repo lint --since origin/master", ++ "lint:all": "backstage-cli repo lint", + "prettier:check": "prettier --check .", +- "create-plugin": "backstage-cli create-plugin --scope internal", +- "remove-plugin": "backstage-cli remove-plugin" ++ "new": "backstage-cli new --scope internal" + }, +@@ -32,7 +33,15 @@ + "devDependencies": { +- "@backstage/cli": "^0.10.1", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/e2e-test-utils": "^0.1.0", ++ "@playwright/test": "^1.32.3", + "@spotify/prettier-config": "^12.0.0", +- "concurrently": "^6.0.0", +- "lerna": "^4.0.0", +- "prettier": "^2.3.2" ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^9.0.0", ++ "prettier": "^2.3.2", ++ "typescript": "~5.2.0" ++ }, ++ "resolutions": { ++ "@types/react": "^17", ++ "@types/react-dom": "^17" + }, +@@ -40,3 +49,3 @@ + "lint-staged": { +- "*.{js,jsx,ts,tsx}": [ ++ "*.{js,jsx,ts,tsx,mjs,cjs}": [ + "eslint --fix", +diff --git a/packages/README.md b/packages/README.md +new file mode 100644 +index 0000000..6327fa0 +--- /dev/null ++++ b/packages/README.md +@@ -0,0 +1,9 @@ ++# The Packages Folder ++ ++This is where your own applications and centrally managed libraries live, each ++in a separate folder of its own. ++ ++From the start there's an `app` folder (for the frontend) and a `backend` folder ++(for the Node backend), but you can also add more modules in here that house ++your core additions and adaptations, such as themes, common React component ++libraries, utilities, and similar. +diff --git a/packages/app/.eslintignore b/packages/app/.eslintignore +new file mode 100644 +index 0000000..a48cf0d +--- /dev/null ++++ b/packages/app/.eslintignore +@@ -0,0 +1 @@ ++public +diff --git a/packages/app/.eslintrc.js b/packages/app/.eslintrc.js +index 13573ef..e2a53a6 100644 +--- a/packages/app/.eslintrc.js ++++ b/packages/app/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/app/cypress.json b/packages/app/cypress.json +deleted file mode 100644 +index 5de7ebf..0000000 +--- a/packages/app/cypress.json ++++ /dev/null +@@ -1,5 +0,0 @@ +-{ +- "baseUrl": "http://localhost:3001", +- "fixturesFolder": false, +- "pluginsFile": false +-} +diff --git a/packages/app/cypress/.eslintrc.json b/packages/app/cypress/.eslintrc.json +deleted file mode 100644 +index 2b3a458..0000000 +--- a/packages/app/cypress/.eslintrc.json ++++ /dev/null +@@ -1,21 +0,0 @@ +-{ +- "plugins": ["cypress"], +- "extends": ["plugin:cypress/recommended"], +- "rules": { +- "jest/expect-expect": [ +- "error", +- { +- "assertFunctionNames": ["expect", "cy.contains"] +- } +- ], +- "import/no-extraneous-dependencies": [ +- "error", +- { +- "devDependencies": true, +- "optionalDependencies": true, +- "peerDependencies": true, +- "bundledDependencies": true +- } +- ] +- } +-} +diff --git a/packages/app/cypress/integration/app.js b/packages/app/cypress/integration/app.js +deleted file mode 100644 +index 43fb2e3..0000000 +--- a/packages/app/cypress/integration/app.js ++++ /dev/null +@@ -1,6 +0,0 @@ +-describe('App', () => { +- it('should render the catalog', () => { +- cy.visit('/'); +- cy.contains('My Company Catalog'); +- }); +-}); +diff --git a/packages/app/e2e-tests/app.test.ts b/packages/app/e2e-tests/app.test.ts +new file mode 100644 +index 0000000..d45bc0d +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,23 @@ ++/* ++ * Copyright 2020 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { test, expect } from '@playwright/test'; ++ ++test('App should render the welcome page', async ({ page }) => { ++ await page.goto('/'); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 3243e81..2100405 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,22 +5,38 @@ + "bundled": true, ++ "backstage": { ++ "role": "frontend" ++ }, ++ "scripts": { ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "clean": "backstage-cli package clean", ++ "test": "backstage-cli package test", ++ "lint": "backstage-cli package lint" ++ }, + "dependencies": { +- "@backstage/app-defaults": "^0.1.2", +- "@backstage/catalog-model": "^0.9.7", +- "@backstage/cli": "^0.10.1", +- "@backstage/core-app-api": "^0.2.0", +- "@backstage/core-components": "^0.8.0", +- "@backstage/core-plugin-api": "^0.3.0", +- "@backstage/integration-react": "^0.1.15", +- "@backstage/plugin-api-docs": "^0.6.18", +- "@backstage/plugin-catalog": "^0.7.4", +- "@backstage/plugin-catalog-import": "^0.7.5", +- "@backstage/plugin-catalog-react": "^0.6.5", +- "@backstage/plugin-github-actions": "^0.4.26", +- "@backstage/plugin-org": "^0.3.30", +- "@backstage/plugin-scaffolder": "^0.11.14", +- "@backstage/plugin-search": "^0.5.1", +- "@backstage/plugin-tech-radar": "^0.4.13", +- "@backstage/plugin-techdocs": "^0.12.9", +- "@backstage/plugin-user-settings": "^0.3.13", +- "@backstage/theme": "^0.2.14", ++ "@backstage/app-defaults": "^1.4.5-next.1", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/core-app-api": "^1.11.1-next.0", ++ "@backstage/core-components": "^0.13.8-next.1", ++ "@backstage/core-plugin-api": "^1.8.0-next.0", ++ "@backstage/integration-react": "^1.1.21-next.1", ++ "@backstage/plugin-api-docs": "^0.9.13-next.1", ++ "@backstage/plugin-catalog": "^1.15.0-next.1", ++ "@backstage/plugin-catalog-common": "^1.0.17", ++ "@backstage/plugin-catalog-graph": "^0.2.38-next.1", ++ "@backstage/plugin-catalog-import": "^0.10.2-next.1", ++ "@backstage/plugin-catalog-react": "^1.9.0-next.1", ++ "@backstage/plugin-github-actions": "^0.6.7-next.1", ++ "@backstage/plugin-org": "^0.6.16-next.1", ++ "@backstage/plugin-permission-react": "^0.4.17-next.0", ++ "@backstage/plugin-scaffolder": "^1.16.0-next.1", ++ "@backstage/plugin-search": "^1.4.2-next.1", ++ "@backstage/plugin-search-react": "^1.7.2-next.1", ++ "@backstage/plugin-tech-radar": "^0.6.10-next.1", ++ "@backstage/plugin-techdocs": "^1.8.1-next.1", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.2-next.1", ++ "@backstage/plugin-techdocs-react": "^1.1.13-next.1", ++ "@backstage/plugin-user-settings": "^0.7.12-next.1", ++ "@backstage/theme": "^0.4.4-next.0", + "@material-ui/core": "^4.12.2", +@@ -28,31 +44,17 @@ + "history": "^5.0.0", +- "react": "^16.13.1", +- "react-dom": "^16.13.1", +- "react-router": "6.0.0-beta.0", +- "react-router-dom": "6.0.0-beta.0", +- "react-use": "^15.3.3" ++ "react": "^17.0.2", ++ "react-dom": "^17.0.2", ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^0.1.24", ++ "@backstage/test-utils": "^1.4.5-next.0", ++ "@playwright/test": "^1.32.3", + "@testing-library/jest-dom": "^5.10.1", +- "@testing-library/react": "^10.4.1", +- "@testing-library/user-event": "^12.0.7", +- "@types/jest": "^26.0.7", +- "@types/node": "^14.14.32", ++ "@testing-library/react": "^12.1.3", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^8.0.0", + "@types/react-dom": "*", +- "cross-env": "^7.0.0", +- "cypress": "^7.3.0", +- "eslint-plugin-cypress": "^2.10.3", +- "start-server-and-test": "^1.10.11" +- }, +- "scripts": { +- "start": "backstage-cli app:serve", +- "build": "backstage-cli app:build", +- "clean": "backstage-cli clean", +- "test": "backstage-cli test", +- "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", +- "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", +- "lint": "backstage-cli lint", +- "cy:dev": "cypress open", +- "cy:run": "cypress run" ++ "cross-env": "^7.0.0" + }, +diff --git a/packages/app/public/index.html b/packages/app/public/index.html +index b93896c..fc758ee 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -10,3 +10,2 @@ + /> +- + +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 8a53583..8d62f29 100644 +--- a/packages/app/src/App.tsx ++++ b/packages/app/src/App.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { Navigate, Route } from 'react-router'; ++import { Navigate, Route } from 'react-router-dom'; + import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; +@@ -13,2 +13,3 @@ import { + import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; ++import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +@@ -16,3 +17,2 @@ import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -21,2 +21,4 @@ import { + } from '@backstage/plugin-techdocs'; ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; + import { UserSettingsPage } from '@backstage/plugin-user-settings'; +@@ -29,3 +31,6 @@ import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; + import { createApp } from '@backstage/app-defaults'; +-import { FlatRoutes } from '@backstage/core-app-api'; ++import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; ++import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; ++import { RequirePermission } from '@backstage/plugin-permission-react'; ++import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; + +@@ -37,5 +42,6 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); + bind(apiDocsPlugin.externalRoutes, { +- createComponent: scaffolderPlugin.routes.root, ++ registerApi: catalogImportPlugin.routes.importPage, + }); +@@ -43,2 +49,6 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, ++ }); ++ bind(orgPlugin.externalRoutes, { ++ catalogIndex: catalogPlugin.routes.catalogIndex, + }); +@@ -47,8 +57,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -60,5 +67,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -73,3 +82,10 @@ const routes = ( + /> +- } /> ++ ++ ++ ++ } ++ /> + }> +@@ -78,2 +94,3 @@ const routes = ( + } /> ++ } /> + +@@ -81,4 +98,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -88,5 +105,3 @@ const App = () => ( + +- ++ , + ); +- +-export default App; +diff --git a/packages/app/src/components/Root/LogoFull.tsx b/packages/app/src/components/Root/LogoFull.tsx +index c7b1c84..47e3b73 100644 +--- a/packages/app/src/components/Root/LogoFull.tsx ++++ b/packages/app/src/components/Root/LogoFull.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/LogoIcon.tsx b/packages/app/src/components/Root/LogoIcon.tsx +index 073cf6e..7eae8c7 100644 +--- a/packages/app/src/components/Root/LogoIcon.tsx ++++ b/packages/app/src/components/Root/LogoIcon.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx +index b4fa04f..6768b48 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,19 +1,3 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- +-import React, { useContext, PropsWithChildren } from 'react'; +-import { Link, makeStyles } from '@material-ui/core'; ++import React, { PropsWithChildren } from 'react'; ++import { makeStyles } from '@material-ui/core'; + import HomeIcon from '@material-ui/icons/Home'; +@@ -25,18 +9,21 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; +-import { Settings as SidebarSettings } from '@backstage/plugin-user-settings'; + import { +- SidebarSearchModal, +- SearchContextProvider, +-} from '@backstage/plugin-search'; ++ Settings as SidebarSettings, ++ UserSettingsSignInAvatar, ++} from '@backstage/plugin-user-settings'; ++import { SidebarSearchModal } from '@backstage/plugin-search'; + import { + Sidebar, +- SidebarPage, + sidebarConfig, +- SidebarContext, +- SidebarItem, + SidebarDivider, +- SidebarSpace, ++ SidebarGroup, ++ SidebarItem, ++ SidebarPage, + SidebarScrollWrapper, ++ SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; ++import MenuIcon from '@material-ui/icons/Menu'; ++import SearchIcon from '@material-ui/icons/Search'; + +@@ -59,3 +46,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -63,8 +50,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -79,19 +61,27 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ } to="/search"> + +- ++ + +- {/* Global nav, not org-specific */} +- +- +- +- +- {/* End global nav */} +- +- +- +- ++ }> ++ {/* Global nav, not org-specific */} ++ ++ ++ ++ ++ {/* End global nav */} ++ ++ ++ ++ ++ + + +- ++ } ++ to="/settings" ++ > ++ ++ + +diff --git a/packages/app/src/components/Root/index.ts b/packages/app/src/components/Root/index.ts +index dff706f..3528881 100644 +--- a/packages/app/src/components/Root/index.ts ++++ b/packages/app/src/components/Root/index.ts +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + export { Root } from './Root'; +diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx +index d3b4b78..6722ea2 100644 +--- a/packages/app/src/components/catalog/EntityPage.tsx ++++ b/packages/app/src/components/catalog/EntityPage.tsx +@@ -1,16 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ + import React from 'react'; +@@ -29,3 +14,2 @@ import { + EntityDependsOnResourcesCard, +- EntitySystemDiagramCard, + EntityHasComponentsCard, +@@ -43,2 +27,4 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +@@ -56,2 +42,27 @@ import { EntityTechdocsContent } from '@backstage/plugin-techdocs'; + import { EmptyState } from '@backstage/core-components'; ++import { ++ Direction, ++ EntityCatalogGraphCard, ++} from '@backstage/plugin-catalog-graph'; ++import { ++ RELATION_API_CONSUMED_BY, ++ RELATION_API_PROVIDED_BY, ++ RELATION_CONSUMES_API, ++ RELATION_DEPENDENCY_OF, ++ RELATION_DEPENDS_ON, ++ RELATION_HAS_PART, ++ RELATION_PART_OF, ++ RELATION_PROVIDES_API, ++} from '@backstage/catalog-model'; ++ ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); + +@@ -94,2 +105,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -110,2 +129,6 @@ const overviewContent = ( + ++ ++ ++ ++ + +@@ -152,3 +175,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -179,3 +202,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -198,3 +221,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -225,2 +248,5 @@ const apiPage = ( + ++ ++ ++ + +@@ -292,3 +318,9 @@ const systemPage = ( + +- ++ ++ ++ ++ ++ ++ ++ + +@@ -304,3 +336,19 @@ const systemPage = ( + +- ++ + +@@ -317,2 +365,5 @@ const domainPage = ( + ++ ++ ++ + +diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx +index 50ffbad..1788dde 100644 +--- a/packages/app/src/components/search/SearchPage.tsx ++++ b/packages/app/src/components/search/SearchPage.tsx +@@ -1,7 +1,12 @@ + import React from 'react'; +-import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; ++import { makeStyles, Theme, Grid, Paper } from '@material-ui/core'; + +-import { CatalogResultListItem } from '@backstage/plugin-catalog'; +-import { DocsResultListItem } from '@backstage/plugin-techdocs'; ++import { CatalogSearchResultListItem } from '@backstage/plugin-catalog'; ++import { ++ catalogApiRef, ++ CATALOG_FILTER_EXISTS, ++} from '@backstage/plugin-catalog-react'; ++import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; + ++import { SearchType } from '@backstage/plugin-search'; + import { +@@ -10,6 +15,13 @@ import { + SearchResult, +- SearchType, +- DefaultResultListItem, +-} from '@backstage/plugin-search'; +-import { Content, Header, Page } from '@backstage/core-components'; ++ SearchPagination, ++ useSearch, ++} from '@backstage/plugin-search-react'; ++import { ++ CatalogIcon, ++ Content, ++ DocsIcon, ++ Header, ++ Page, ++} from '@backstage/core-components'; ++import { useApi } from '@backstage/core-plugin-api'; + +@@ -21,2 +33,3 @@ const useStyles = makeStyles((theme: Theme) => ({ + padding: theme.spacing(2), ++ marginTop: theme.spacing(2), + }, +@@ -31,2 +44,4 @@ const SearchPage = () => { + const classes = useStyles(); ++ const { types } = useSearch(); ++ const catalogApi = useApi(catalogApiRef); + +@@ -39,3 +54,3 @@ const SearchPage = () => { + +- ++ + +@@ -43,10 +58,43 @@ const SearchPage = () => { + ++ , ++ }, ++ { ++ value: 'techdocs', ++ name: 'Documentation', ++ icon: , ++ }, ++ ]} ++ /> + +- ++ {types.includes('techdocs') && ( ++ { ++ // Return a list of entities which are documented. ++ const { items } = await catalogApi.getEntities({ ++ fields: ['metadata.name'], ++ filter: { ++ 'metadata.annotations.backstage.io/techdocs-ref': ++ CATALOG_FILTER_EXISTS, ++ }, ++ }); ++ ++ const names = items.map(entity => entity.metadata.name); ++ names.sort(); ++ return names; ++ }} ++ /> ++ )} + { + className={classes.filter} ++ label="Lifecycle" + name="lifecycle" +@@ -62,32 +111,6 @@ const SearchPage = () => { + ++ + +- {({ results }) => ( +- +- {results.map(({ type, document }) => { +- switch (type) { +- case 'software-catalog': +- return ( +- +- ); +- case 'techdocs': +- return ( +- +- ); +- default: +- return ( +- +- ); +- } +- })} +- +- )} ++ } /> ++ } /> + +diff --git a/packages/backend/.eslintrc.js b/packages/backend/.eslintrc.js +index 16a033d..e2a53a6 100644 +--- a/packages/backend/.eslintrc.js ++++ b/packages/backend/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint.backend')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index 31231a3..18548e9 100644 +--- a/packages/backend/Dockerfile ++++ b/packages/backend/Dockerfile +@@ -7,3 +7,3 @@ + # yarn tsc +-# yarn build ++# yarn build:backend + # +@@ -11,6 +11,29 @@ + +-FROM node:14-buster-slim ++FROM node:18-bookworm-slim + ++# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends python3 g++ build-essential && \ ++ yarn config set python /usr/bin/python3 ++ ++# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, ++# in which case you should also move better-sqlite3 to "devDependencies" in package.json. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends libsqlite3-dev ++ ++# From here on we use the least-privileged `node` user to run the backend. ++USER node ++ ++# This should create the app dir as `node`. ++# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. ++# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. + WORKDIR /app + ++# This switches many Node.js dependencies to production mode. ++ENV NODE_ENV production ++ + # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. +@@ -18,11 +41,12 @@ WORKDIR /app + # and along with yarn.lock and the root package.json, that's enough to run yarn install. +-COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ ++COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ + RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz + +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + + # Then copy the rest of the backend bundle, along with any other files we might want. +-COPY packages/backend/dist/bundle.tar.gz app-config.yaml ./ ++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"] +diff --git a/packages/backend/README.md b/packages/backend/README.md +index 02426ef..3607b0a 100644 +--- a/packages/backend/README.md ++++ b/packages/backend/README.md +@@ -18,4 +18,2 @@ To run the example backend, first go to the project root and run + yarn install +-yarn tsc +-yarn build + ``` +@@ -27,7 +25,2 @@ After that, go to the `packages/backend` directory and run + ```bash +-AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x \ +-AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x \ +-AUTH_OAUTH2_CLIENT_ID=x AUTH_OAUTH2_CLIENT_SECRET=x \ +-AUTH_OAUTH2_AUTH_URL=x AUTH_OAUTH2_TOKEN_URL=x \ +-LOG_LEVEL=debug \ + yarn start +@@ -35,4 +28,4 @@ yarn start + +-Substitute `x` for actual values, or leave them as dummy values just to try out +-the backend without using the auth or sentry features. ++If you want to override any configuration locally, for example adding any secrets, ++you can do so in `app-config.local.yaml`. + +@@ -45,3 +38,3 @@ locations to the backend. These are places where the backend can find some + entity descriptor data to consume and serve. For more information, see +-[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog). ++[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). + +@@ -65,2 +58,2 @@ and + - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +-- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) ++- [Backstage Documentation](https://backstage.io/docs) +diff --git a/packages/backend/package.json b/packages/backend/package.json +index 709d421..f224eaf 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,27 +6,33 @@ + "private": true, ++ "backstage": { ++ "role": "backend" ++ }, + "scripts": { +- "build": "backstage-cli backend:bundle", +- "build-image": "docker build ../.. -f Dockerfile --tag backstage", +- "start": "backstage-cli backend:dev", +- "lint": "backstage-cli lint", +- "test": "backstage-cli test", +- "clean": "backstage-cli clean", +- "migrate:create": "knex migrate:make -x ts" ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "lint": "backstage-cli package lint", ++ "test": "backstage-cli package test", ++ "clean": "backstage-cli package clean", ++ "build-image": "docker build ../.. -f Dockerfile --tag backstage" + }, + "dependencies": { +- "app": "0.0.0", +- "@backstage/backend-common": "^0.9.13", +- "@backstage/catalog-model": "^0.9.7", +- "@backstage/catalog-client": "^0.5.2", +- "@backstage/config": "^0.1.11", +- "@backstage/plugin-app-backend": "^0.3.19", +- "@backstage/plugin-auth-backend": "^0.5.0", +- "@backstage/plugin-catalog-backend": "^0.19.1", +- "@backstage/plugin-proxy-backend": "^0.2.14", +- "@backstage/plugin-scaffolder-backend": "^0.15.16", +- "@backstage/plugin-search-backend": "^0.2.8", +- "@backstage/plugin-search-backend-node": "^0.4.3", +- "@backstage/plugin-techdocs-backend": "^0.12.0", +- "@gitbeaker/node": "^34.6.0", +- "@octokit/rest": "^18.5.3", ++ "@backstage/backend-common": "^0.19.9-next.1", ++ "@backstage/backend-tasks": "^0.5.12-next.1", ++ "@backstage/catalog-client": "^1.4.5", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/config": "^1.1.1", ++ "@backstage/plugin-app-backend": "^0.3.55-next.1", ++ "@backstage/plugin-auth-backend": "^0.20.0-next.1", ++ "@backstage/plugin-auth-node": "^0.4.1-next.1", ++ "@backstage/plugin-catalog-backend": "^1.15.0-next.1", ++ "@backstage/plugin-permission-common": "^0.7.9", ++ "@backstage/plugin-permission-node": "^0.7.18-next.1", ++ "@backstage/plugin-proxy-backend": "^0.4.5-next.1", ++ "@backstage/plugin-scaffolder-backend": "^1.19.0-next.1", ++ "@backstage/plugin-search-backend": "^1.4.7-next.1", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.16-next.1", ++ "@backstage/plugin-search-backend-node": "^1.2.11-next.1", ++ "@backstage/plugin-techdocs-backend": "^1.9.0-next.1", ++ "app": "link:../app", ++ "better-sqlite3": "^8.0.0", + "dockerode": "^3.3.1", +@@ -34,4 +40,3 @@ + "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "pg": "^8.3.0", + "winston": "^3.2.1" +@@ -39,6 +44,7 @@ + "devDependencies": { +- "@backstage/cli": "^0.10.1", ++ "@backstage/cli": "^0.24.0-next.1", + "@types/dockerode": "^3.3.0", + "@types/express": "^4.17.6", +- "@types/express-serve-static-core": "^4.17.5" ++ "@types/express-serve-static-core": "^4.17.5", ++ "@types/luxon": "^2.0.4" + }, +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 3f12122..04c4ff9 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -17,3 +17,3 @@ import { + DatabaseManager, +- SingleHostDiscovery, ++ HostDiscovery, + UrlReaders, +@@ -21,2 +21,3 @@ import { + } from '@backstage/backend-common'; ++import { TaskScheduler } from '@backstage/backend-tasks'; + import { Config } from '@backstage/config'; +@@ -30,2 +31,4 @@ import search from './plugins/search'; + import { PluginEnvironment } from './types'; ++import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; + +@@ -34,9 +37,17 @@ function makeCreateEnv(config: Config) { + const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); +- +- root.info(`Created UrlReader ${reader}`); +- ++ const discovery = HostDiscovery.fromConfig(config); + const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++ const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); + const tokenManager = ServerTokenManager.noop(); ++ const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); ++ ++ const identity = DefaultIdentityClient.create({ ++ discovery, ++ }); ++ const permissions = ServerPermissionClient.fromConfig(config, { ++ discovery, ++ tokenManager, ++ }); ++ ++ root.info(`Created UrlReader ${reader}`); + +@@ -46,3 +57,15 @@ function makeCreateEnv(config: Config) { + const cache = cacheManager.forPlugin(plugin); +- return { logger, database, cache, config, reader, discovery, tokenManager }; ++ const scheduler = taskScheduler.forPlugin(plugin); ++ return { ++ logger, ++ database, ++ cache, ++ config, ++ reader, ++ discovery, ++ tokenManager, ++ scheduler, ++ permissions, ++ identity, ++ }; + }; +@@ -72,2 +95,4 @@ async function main() { + apiRouter.use('/search', await search(searchEnv)); ++ ++ // Add backends ABOVE this line; this 404 handler is the catch-all fallback + apiRouter.use(notFoundHandler()); +@@ -87,3 +112,3 @@ module.hot?.accept(); + main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); ++ console.error('Backend failed to start up', error); + process.exit(1); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +index 07fb04f..7c37f68 100644 +--- a/packages/backend/src/plugins/app.ts ++++ b/packages/backend/src/plugins/app.ts +@@ -4,9 +4,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + return await createRouter({ +- logger, +- config, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, + appPackageName: 'app', +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +index 5216510..77eb6aa 100644 +--- a/packages/backend/src/plugins/auth.ts ++++ b/packages/backend/src/plugins/auth.ts +@@ -1,2 +1,6 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; ++import { ++ createRouter, ++ providers, ++ defaultAuthProviderFactories, ++} from '@backstage/plugin-auth-backend'; + import { Router } from 'express'; +@@ -4,9 +8,47 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, database, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, ++ providerFactories: { ++ ...defaultAuthProviderFactories, ++ ++ // This replaces the default GitHub auth provider with a customized one. ++ // The `signIn` option enables sign-in for this provider, using the ++ // identity resolution logic that's provided in the `resolver` callback. ++ // ++ // This particular resolver makes all users share a single "guest" identity. ++ // It should only be used for testing and trying out Backstage. ++ // ++ // If you want to use a production ready resolver you can switch to ++ // the one that is commented out below, it looks up a user entity in the ++ // catalog using the GitHub username of the authenticated user. ++ // That resolver requires you to have user entities populated in the catalog, ++ // for example using https://backstage.io/docs/integrations/github/org ++ // ++ // There are other resolvers to choose from, and you can also create ++ // your own, see the auth documentation for more details: ++ // ++ // https://backstage.io/docs/auth/identity-resolver ++ github: providers.github.create({ ++ signIn: { ++ resolver(_, ctx) { ++ const userRef = 'user:default/guest'; // Must be a full entity reference ++ return ctx.issueToken({ ++ claims: { ++ sub: userRef, // The user's own identity ++ ent: [userRef], // A list of identities that the user claims ownership through ++ }, ++ }); ++ }, ++ // resolver: providers.github.resolvers.usernameMatchingUserEntityName(), ++ }, ++ }), ++ }, ++ }); + } +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +index 506f6d9..54ec393 100644 +--- a/packages/backend/src/plugins/proxy.ts ++++ b/packages/backend/src/plugins/proxy.ts +@@ -4,8 +4,10 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ }); + } +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +index 6be2e97..a12fee2 100644 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ b/packages/backend/src/plugins/scaffolder.ts +@@ -1,5 +1,3 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; + import { CatalogClient } from '@backstage/catalog-client'; + import { createRouter } from '@backstage/plugin-scaffolder-backend'; +-import Docker from 'dockerode'; + import { Router } from 'express'; +@@ -7,20 +5,17 @@ import type { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- database, +- reader, +- discovery, +-}: PluginEnvironment): Promise { +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- const catalogClient = new CatalogClient({ discoveryApi: discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ const catalogClient = new CatalogClient({ ++ discoveryApi: env.discovery, ++ }); + + return await createRouter({ +- containerRunner, +- logger, +- config, +- database, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ reader: env.reader, + catalogClient, +- reader, ++ identity: env.identity, ++ permissions: env.permissions, + }); +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +index f23b0c7..e9469dc 100644 +--- a/packages/backend/src/plugins/search.ts ++++ b/packages/backend/src/plugins/search.ts +@@ -7,14 +7,25 @@ import { + import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend'; ++import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend'; ++import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; ++import { Router } from 'express'; + +-export default async function createPlugin({ +- logger, +- discovery, +- config, +- tokenManager, +-}: PluginEnvironment) { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); ++ const searchEngine = new LunrSearchEngine({ ++ logger: env.logger, ++ }); ++ const indexBuilder = new IndexBuilder({ ++ logger: env.logger, ++ searchEngine, ++ }); ++ ++ const schedule = env.scheduler.createScheduledTaskRunner({ ++ frequency: { minutes: 10 }, ++ timeout: { minutes: 15 }, ++ // A 3 second delay gives the backend server a chance to initialize before ++ // any collators are executed, which may attempt requests against the API. ++ initialDelay: { seconds: 3 }, ++ }); + +@@ -23,6 +34,6 @@ export default async function createPlugin({ + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { +- discovery, +- tokenManager, ++ schedule, ++ factory: DefaultCatalogCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, + }), +@@ -32,7 +43,7 @@ export default async function createPlugin({ + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultTechDocsCollator.fromConfig(config, { +- discovery, +- logger, +- tokenManager, ++ schedule, ++ factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ logger: env.logger, ++ tokenManager: env.tokenManager, + }), +@@ -43,6 +54,4 @@ export default async function createPlugin({ + const { scheduler } = await indexBuilder.build(); ++ scheduler.start(); + +- // A 3 second delay gives the backend server a chance to initialize before +- // any collators are executed, which may attempt requests against the API. +- setTimeout(() => scheduler.start(), 3000); + useHotCleanup(module, () => scheduler.stop()); +@@ -51,3 +60,6 @@ export default async function createPlugin({ + engine: indexBuilder.getSearchEngine(), +- logger, ++ types: indexBuilder.getDocumentTypes(), ++ permissions: env.permissions, ++ config: env.config, ++ logger: env.logger, + }); +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +index 054c64d..be8bb0c 100644 +--- a/packages/backend/src/plugins/techdocs.ts ++++ b/packages/backend/src/plugins/techdocs.ts +@@ -11,13 +11,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +- cache, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, ++ const preparers = await Preparers.fromConfig(env.config, { ++ logger: env.logger, ++ reader: env.reader, + }); +@@ -29,4 +25,4 @@ export default async function createPlugin({ + // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, ++ const generators = await Generators.fromConfig(env.config, { ++ logger: env.logger, + containerRunner, +@@ -37,5 +33,5 @@ export default async function createPlugin({ + // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, ++ const publisher = await Publisher.fromConfig(env.config, { ++ logger: env.logger, ++ discovery: env.discovery, + }); +@@ -49,6 +45,6 @@ export default async function createPlugin({ + publisher, +- logger, +- config, +- discovery, +- cache, ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ cache: env.cache, + }); +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +index b1e2e0a..9cd2c74 100644 +--- a/packages/backend/src/types.ts ++++ b/packages/backend/src/types.ts +@@ -9,2 +9,5 @@ import { + } from '@backstage/backend-common'; ++import { PluginTaskScheduler } from '@backstage/backend-tasks'; ++import { PermissionEvaluator } from '@backstage/plugin-permission-common'; ++import { IdentityApi } from '@backstage/plugin-auth-node'; + +@@ -18,2 +21,5 @@ export type PluginEnvironment = { + tokenManager: TokenManager; ++ scheduler: PluginTaskScheduler; ++ permissions: PermissionEvaluator; ++ identity: IdentityApi; + }; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..37c7fb1 +--- /dev/null ++++ b/playwright.config.ts +@@ -0,0 +1,60 @@ ++/* ++ * Copyright 2023 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { defineConfig } from '@playwright/test'; ++import { generateProjects } from '@backstage/e2e-test-utils/playwright'; ++ ++/** ++ * See https://playwright.dev/docs/test-configuration. ++ */ ++export default defineConfig({ ++ timeout: 60_000, ++ ++ expect: { ++ timeout: 5_000, ++ }, ++ ++ // Run your local dev server before starting the tests ++ webServer: process.env.CI ++ ? [] ++ : [ ++ { ++ command: 'yarn start', ++ port: 3000, ++ reuseExistingServer: true, ++ timeout: 60_000, ++ }, ++ ], ++ ++ forbidOnly: !!process.env.CI, ++ ++ retries: process.env.CI ? 2 : 0, ++ ++ reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]], ++ ++ use: { ++ actionTimeout: 0, ++ baseURL: ++ process.env.PLAYWRIGHT_URL ?? ++ (process.env.CI ? 'http://localhost:7007' : 'http://localhost:3000'), ++ screenshot: 'only-on-failure', ++ trace: 'on-first-retry', ++ }, ++ ++ outputDir: 'node_modules/.cache/e2e-test-results', ++ ++ projects: generateProjects(), // Find all packages with e2e-test folders ++}); +diff --git a/plugins/README.md b/plugins/README.md +new file mode 100644 +index 0000000..d7865fd +--- /dev/null ++++ b/plugins/README.md +@@ -0,0 +1,9 @@ ++# The Plugins Folder ++ ++This is where your own plugins and their associated modules live, each in a ++separate folder of its own. ++ ++If you want to create a new plugin here, go to your project root directory, run ++the command `yarn new`, and follow the on-screen instructions. ++ ++You can also check out existing plugins on [the plugin marketplace](https://backstage.io/plugins)! +diff --git a/yarn.lock b/yarn.lock +new file mode 100644 +index 0000000..5ad7fe2 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1 @@ ++# intentionally left empty diff --git a/diffs/0.4.8..0.5.7-next.1.diff b/diffs/0.4.8..0.5.7-next.1.diff new file mode 100644 index 00000000..a9f7e7a6 --- /dev/null +++ b/diffs/0.4.8..0.5.7-next.1.diff @@ -0,0 +1,1903 @@ +diff --git a/.dockerignore b/.dockerignore +index 63c9c34..05edb62 100644 +--- a/.dockerignore ++++ b/.dockerignore +@@ -1,5 +1,8 @@ + .git ++.yarn/cache ++.yarn/install-state.gz + node_modules +-packages +-!packages/backend/dist ++packages/*/src ++packages/*/node_modules + plugins ++*.local.yaml +diff --git a/.eslintignore b/.eslintignore +new file mode 100644 +index 0000000..e5b1994 +--- /dev/null ++++ b/.eslintignore +@@ -0,0 +1 @@ ++playwright.config.ts +diff --git a/.gitignore b/.gitignore +index d16a8d3..fbf8139 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -17,2 +17,11 @@ node_modules/ + ++# Yarn 3 files ++.pnp.* ++.yarn/* ++!.yarn/patches ++!.yarn/plugins ++!.yarn/releases ++!.yarn/sdks ++!.yarn/versions ++ + # Node version directives +@@ -39 +48,7 @@ site + *-credentials.yaml ++ ++# vscode database functionality support files ++*.session.sql ++ ++# E2E test reports ++e2e-test-report/ +diff --git a/app-config.production.yaml b/app-config.production.yaml +index 5e36c23..8f0751c 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -1,3 +1,3 @@ + app: +- # Should be the same as backend.baseUrl when using the `app-backend` plugin ++ # Should be the same as backend.baseUrl when using the `app-backend` plugin. + baseUrl: http://localhost:7007 +@@ -5,4 +5,31 @@ app: + backend: ++ # Note that the baseUrl should be the URL that the browser and other clients ++ # should use when communicating with the backend, i.e. it needs to be ++ # reachable not just from within the backend host, but from all of your ++ # callers. When its value is "http://localhost:7007", it's strictly private ++ # and can't be reached by others. + baseUrl: http://localhost:7007 +- listen: +- port: 7007 ++ # The listener can also be expressed as a single : string. In this case we bind to ++ # all interfaces, the most permissive setting. The right value depends on your specific deployment. ++ listen: ':7007' ++ ++ # config options: https://node-postgres.com/api/client ++ database: ++ client: pg ++ connection: ++ host: ${POSTGRES_HOST} ++ port: ${POSTGRES_PORT} ++ user: ${POSTGRES_USER} ++ password: ${POSTGRES_PASSWORD} ++ # https://node-postgres.com/features/ssl ++ # you can set the sslmode configuration option via the `PGSSLMODE` environment variable ++ # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require) ++ # ssl: ++ # ca: # if you have a CA file and want to verify it you can uncomment this section ++ # $file: /ca/server.crt ++ ++catalog: ++ # Overrides the default list locations from app-config.yaml as these contain example data. ++ # See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details ++ # on how to get entities into the catalog. ++ locations: [] +diff --git a/app-config.yaml b/app-config.yaml +index 1a622a2..08d2abf 100644 +--- a/app-config.yaml ++++ b/app-config.yaml +@@ -9,3 +9,4 @@ backend: + # Used for enabling authentication, secret is shared by all backend plugins +- # See backend-to-backend-auth.md in the docs for information on the format ++ # See https://backstage.io/docs/auth/service-to-service-auth for ++ # information on the format + # auth: +@@ -16,2 +17,4 @@ backend: + port: 7007 ++ # Uncomment the following host directive to bind to specific interfaces ++ # host: 127.0.0.1 + csp: +@@ -22,9 +25,9 @@ backend: + origin: http://localhost:3000 +- methods: [GET, POST, PUT, DELETE] ++ methods: [GET, HEAD, PATCH, POST, PUT, DELETE] + credentials: true ++ # This is for local development only, it is not recommended to use this in production ++ # The production database configuration is stored in app-config.production.yaml + database: +- client: sqlite3 ++ client: better-sqlite3 + connection: ':memory:' +- cache: +- store: memory + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir +@@ -34,2 +37,4 @@ integrations: + - host: github.com ++ # This is a Personal Access Token or PAT from GitHub. You can find out how to generate this token, and more information ++ # about setting up the GitHub integration here: https://backstage.io/docs/getting-started/configuration#setting-up-a-github-integration + token: ${GITHUB_TOKEN} +@@ -41,5 +46,8 @@ integrations: + proxy: +- '/test': +- target: 'https://example.com' +- changeOrigin: true ++ ### Example for how to add a proxy endpoint for the frontend. ++ ### A typical reason to do this is to handle HTTPS and CORS for internal services. ++ # endpoints: ++ # '/test': ++ # target: 'https://example.com' ++ # changeOrigin: true + +@@ -64,41 +72,32 @@ scaffolder: + catalog: ++ import: ++ entityFilename: catalog-info.yaml ++ pullRequestBranchName: backstage-integration + rules: +- - allow: [Component, System, API, Group, User, Resource, Location] ++ - allow: [Component, System, API, Resource, Location] + locations: +- # Backstage example components +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-components.yaml ++ # Local example data, file locations are relative to the backend process, typically `packages/backend` ++ - type: file ++ target: ../../examples/entities.yaml + +- # Backstage example systems +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-systems.yaml +- +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml +- +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml +- +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.yaml +- +- # Backstage example templates +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/react-ssr-template/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/springboot-grpc-template/template.yaml ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml + rules: + - allow: [Template] +- - type: url +- target: https://github.com/spotify/cookiecutter-golang/blob/master/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/docs-template/template.yaml ++ ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml + rules: +- - allow: [Template] ++ - allow: [User, Group] ++ ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml ++ ++ ## Uncomment these lines to add an example org ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme-corp.yaml ++ # rules: ++ # - allow: [User, Group] +diff --git a/backstage.json b/backstage.json +index 87a2209..707af29 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "0.4.8" ++ "version": "1.20.0-next.1" + } +diff --git a/examples/entities.yaml b/examples/entities.yaml +new file mode 100644 +index 0000000..447e8b1 +--- /dev/null ++++ b/examples/entities.yaml +@@ -0,0 +1,41 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system ++apiVersion: backstage.io/v1alpha1 ++kind: System ++metadata: ++ name: examples ++spec: ++ owner: guests ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: example-website ++spec: ++ type: website ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ providesApis: [example-grpc-api] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api ++apiVersion: backstage.io/v1alpha1 ++kind: API ++metadata: ++ name: example-grpc-api ++spec: ++ type: grpc ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ definition: | ++ syntax = "proto3"; ++ ++ service Exampler { ++ rpc Example (ExampleMessage) returns (ExampleMessage) {}; ++ } ++ ++ message ExampleMessage { ++ string example = 1; ++ }; +diff --git a/examples/org.yaml b/examples/org.yaml +new file mode 100644 +index 0000000..a10e81f +--- /dev/null ++++ b/examples/org.yaml +@@ -0,0 +1,17 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user ++apiVersion: backstage.io/v1alpha1 ++kind: User ++metadata: ++ name: guest ++spec: ++ memberOf: [guests] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group ++apiVersion: backstage.io/v1alpha1 ++kind: Group ++metadata: ++ name: guests ++spec: ++ type: team ++ children: [] +diff --git a/examples/template/content/catalog-info.yaml b/examples/template/content/catalog-info.yaml +new file mode 100644 +index 0000000..d4ccca4 +--- /dev/null ++++ b/examples/template/content/catalog-info.yaml +@@ -0,0 +1,8 @@ ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: ${{ values.name | dump }} ++spec: ++ type: service ++ owner: user:guest ++ lifecycle: experimental +diff --git a/examples/template/content/index.js b/examples/template/content/index.js +new file mode 100644 +index 0000000..071ce5a +--- /dev/null ++++ b/examples/template/content/index.js +@@ -0,0 +1 @@ ++console.log('Hello from ${{ values.name }}!'); +diff --git a/examples/template/content/package.json b/examples/template/content/package.json +new file mode 100644 +index 0000000..86f968a +--- /dev/null ++++ b/examples/template/content/package.json +@@ -0,0 +1,5 @@ ++{ ++ "name": "${{ values.name }}", ++ "private": true, ++ "dependencies": {} ++} +diff --git a/examples/template/template.yaml b/examples/template/template.yaml +new file mode 100644 +index 0000000..33f262b +--- /dev/null ++++ b/examples/template/template.yaml +@@ -0,0 +1,74 @@ ++apiVersion: scaffolder.backstage.io/v1beta3 ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template ++kind: Template ++metadata: ++ name: example-nodejs-template ++ title: Example Node.js Template ++ description: An example template for the scaffolder that creates a simple Node.js service ++spec: ++ owner: user:guest ++ type: service ++ ++ # These parameters are used to generate the input form in the frontend, and are ++ # used to gather input data for the execution of the template. ++ parameters: ++ - title: Fill in some steps ++ required: ++ - name ++ properties: ++ name: ++ title: Name ++ type: string ++ description: Unique name of the component ++ ui:autofocus: true ++ ui:options: ++ rows: 5 ++ - title: Choose a location ++ required: ++ - repoUrl ++ properties: ++ repoUrl: ++ title: Repository Location ++ type: string ++ ui:field: RepoUrlPicker ++ ui:options: ++ allowedHosts: ++ - github.com ++ ++ # These steps are executed in the scaffolder backend, using data that we gathered ++ # via the parameters above. ++ steps: ++ # Each step executes an action, in this case one templates files into the working directory. ++ - id: fetch-base ++ name: Fetch Base ++ action: fetch:template ++ input: ++ url: ./content ++ values: ++ name: ${{ parameters.name }} ++ ++ # This step publishes the contents of the working directory to GitHub. ++ - id: publish ++ name: Publish ++ action: publish:github ++ input: ++ allowedHosts: ['github.com'] ++ description: This is ${{ parameters.name }} ++ repoUrl: ${{ parameters.repoUrl }} ++ ++ # The final step is to register our new component in the catalog. ++ - id: register ++ name: Register ++ action: catalog:register ++ input: ++ repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} ++ catalogInfoPath: '/catalog-info.yaml' ++ ++ # Outputs are displayed to the user after a successful execution of the template. ++ output: ++ links: ++ - title: Repository ++ url: ${{ steps['publish'].output.remoteUrl }} ++ - title: Open in catalog ++ icon: catalog ++ entityRef: ${{ steps['register'].output.entityRef }} +diff --git a/lerna.json b/lerna.json +index 322929d..529a62f 100644 +--- a/lerna.json ++++ b/lerna.json +@@ -3,4 +3,4 @@ + "npmClient": "yarn", +- "useWorkspaces": true, +- "version": "0.1.0" ++ "version": "0.1.0", ++ "$schema": "node_modules/lerna/schemas/lerna-schema.json" + } +diff --git a/package.json b/package.json +index fa4adea..2364da8 100644 +--- a/package.json ++++ b/package.json +@@ -5,3 +5,3 @@ + "engines": { +- "node": "14 || 16" ++ "node": "18 || 20" + }, +@@ -11,3 +11,4 @@ + "start-backend": "yarn workspace backend start", +- "build": "lerna run build", ++ "build:backend": "yarn workspace backend build", ++ "build:all": "backstage-cli repo build --all", + "build-image": "yarn workspace backend build-image", +@@ -15,11 +16,11 @@ + "tsc:full": "tsc --skipLibCheck false --incremental false", +- "clean": "backstage-cli clean && lerna run clean", +- "diff": "lerna run diff --", +- "test": "backstage-cli test", +- "test:all": "lerna run test -- --coverage", +- "lint": "lerna run lint --since origin/master --", +- "lint:all": "lerna run lint --", ++ "clean": "backstage-cli repo clean", ++ "test": "backstage-cli repo test", ++ "test:all": "backstage-cli repo test --coverage", ++ "test:e2e": "playwright test", ++ "fix": "backstage-cli repo fix", ++ "lint": "backstage-cli repo lint --since origin/master", ++ "lint:all": "backstage-cli repo lint", + "prettier:check": "prettier --check .", +- "create-plugin": "backstage-cli create-plugin --scope internal", +- "remove-plugin": "backstage-cli remove-plugin" ++ "new": "backstage-cli new --scope internal" + }, +@@ -32,7 +33,15 @@ + "devDependencies": { +- "@backstage/cli": "^0.10.2", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/e2e-test-utils": "^0.1.0", ++ "@playwright/test": "^1.32.3", + "@spotify/prettier-config": "^12.0.0", +- "concurrently": "^6.0.0", +- "lerna": "^4.0.0", +- "prettier": "^2.3.2" ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^9.0.0", ++ "prettier": "^2.3.2", ++ "typescript": "~5.2.0" ++ }, ++ "resolutions": { ++ "@types/react": "^17", ++ "@types/react-dom": "^17" + }, +diff --git a/packages/README.md b/packages/README.md +new file mode 100644 +index 0000000..6327fa0 +--- /dev/null ++++ b/packages/README.md +@@ -0,0 +1,9 @@ ++# The Packages Folder ++ ++This is where your own applications and centrally managed libraries live, each ++in a separate folder of its own. ++ ++From the start there's an `app` folder (for the frontend) and a `backend` folder ++(for the Node backend), but you can also add more modules in here that house ++your core additions and adaptations, such as themes, common React component ++libraries, utilities, and similar. +diff --git a/packages/app/.eslintignore b/packages/app/.eslintignore +new file mode 100644 +index 0000000..a48cf0d +--- /dev/null ++++ b/packages/app/.eslintignore +@@ -0,0 +1 @@ ++public +diff --git a/packages/app/.eslintrc.js b/packages/app/.eslintrc.js +index 13573ef..e2a53a6 100644 +--- a/packages/app/.eslintrc.js ++++ b/packages/app/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/app/cypress.json b/packages/app/cypress.json +deleted file mode 100644 +index 5de7ebf..0000000 +--- a/packages/app/cypress.json ++++ /dev/null +@@ -1,5 +0,0 @@ +-{ +- "baseUrl": "http://localhost:3001", +- "fixturesFolder": false, +- "pluginsFile": false +-} +diff --git a/packages/app/cypress/.eslintrc.json b/packages/app/cypress/.eslintrc.json +deleted file mode 100644 +index 2b3a458..0000000 +--- a/packages/app/cypress/.eslintrc.json ++++ /dev/null +@@ -1,21 +0,0 @@ +-{ +- "plugins": ["cypress"], +- "extends": ["plugin:cypress/recommended"], +- "rules": { +- "jest/expect-expect": [ +- "error", +- { +- "assertFunctionNames": ["expect", "cy.contains"] +- } +- ], +- "import/no-extraneous-dependencies": [ +- "error", +- { +- "devDependencies": true, +- "optionalDependencies": true, +- "peerDependencies": true, +- "bundledDependencies": true +- } +- ] +- } +-} +diff --git a/packages/app/cypress/integration/app.js b/packages/app/cypress/integration/app.js +deleted file mode 100644 +index 43fb2e3..0000000 +--- a/packages/app/cypress/integration/app.js ++++ /dev/null +@@ -1,6 +0,0 @@ +-describe('App', () => { +- it('should render the catalog', () => { +- cy.visit('/'); +- cy.contains('My Company Catalog'); +- }); +-}); +diff --git a/packages/app/e2e-tests/app.test.ts b/packages/app/e2e-tests/app.test.ts +new file mode 100644 +index 0000000..d45bc0d +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,23 @@ ++/* ++ * Copyright 2020 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { test, expect } from '@playwright/test'; ++ ++test('App should render the welcome page', async ({ page }) => { ++ await page.goto('/'); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 948a4c5..2100405 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,22 +5,38 @@ + "bundled": true, ++ "backstage": { ++ "role": "frontend" ++ }, ++ "scripts": { ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "clean": "backstage-cli package clean", ++ "test": "backstage-cli package test", ++ "lint": "backstage-cli package lint" ++ }, + "dependencies": { +- "@backstage/app-defaults": "^0.1.2", +- "@backstage/catalog-model": "^0.9.8", +- "@backstage/cli": "^0.10.2", +- "@backstage/core-app-api": "^0.2.1", +- "@backstage/core-components": "^0.8.1", +- "@backstage/core-plugin-api": "^0.3.1", +- "@backstage/integration-react": "^0.1.15", +- "@backstage/plugin-api-docs": "^0.6.18", +- "@backstage/plugin-catalog": "^0.7.4", +- "@backstage/plugin-catalog-import": "^0.7.5", +- "@backstage/plugin-catalog-react": "^0.6.7", +- "@backstage/plugin-github-actions": "^0.4.27", +- "@backstage/plugin-org": "^0.3.31", +- "@backstage/plugin-scaffolder": "^0.11.14", +- "@backstage/plugin-search": "^0.5.1", +- "@backstage/plugin-tech-radar": "^0.4.13", +- "@backstage/plugin-techdocs": "^0.12.10", +- "@backstage/plugin-user-settings": "^0.3.13", +- "@backstage/theme": "^0.2.14", ++ "@backstage/app-defaults": "^1.4.5-next.1", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/core-app-api": "^1.11.1-next.0", ++ "@backstage/core-components": "^0.13.8-next.1", ++ "@backstage/core-plugin-api": "^1.8.0-next.0", ++ "@backstage/integration-react": "^1.1.21-next.1", ++ "@backstage/plugin-api-docs": "^0.9.13-next.1", ++ "@backstage/plugin-catalog": "^1.15.0-next.1", ++ "@backstage/plugin-catalog-common": "^1.0.17", ++ "@backstage/plugin-catalog-graph": "^0.2.38-next.1", ++ "@backstage/plugin-catalog-import": "^0.10.2-next.1", ++ "@backstage/plugin-catalog-react": "^1.9.0-next.1", ++ "@backstage/plugin-github-actions": "^0.6.7-next.1", ++ "@backstage/plugin-org": "^0.6.16-next.1", ++ "@backstage/plugin-permission-react": "^0.4.17-next.0", ++ "@backstage/plugin-scaffolder": "^1.16.0-next.1", ++ "@backstage/plugin-search": "^1.4.2-next.1", ++ "@backstage/plugin-search-react": "^1.7.2-next.1", ++ "@backstage/plugin-tech-radar": "^0.6.10-next.1", ++ "@backstage/plugin-techdocs": "^1.8.1-next.1", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.2-next.1", ++ "@backstage/plugin-techdocs-react": "^1.1.13-next.1", ++ "@backstage/plugin-user-settings": "^0.7.12-next.1", ++ "@backstage/theme": "^0.4.4-next.0", + "@material-ui/core": "^4.12.2", +@@ -28,31 +44,17 @@ + "history": "^5.0.0", +- "react": "^16.13.1", +- "react-dom": "^16.13.1", +- "react-router": "6.0.0-beta.0", +- "react-router-dom": "6.0.0-beta.0", +- "react-use": "^15.3.3" ++ "react": "^17.0.2", ++ "react-dom": "^17.0.2", ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^0.1.24", ++ "@backstage/test-utils": "^1.4.5-next.0", ++ "@playwright/test": "^1.32.3", + "@testing-library/jest-dom": "^5.10.1", +- "@testing-library/react": "^10.4.1", +- "@testing-library/user-event": "^12.0.7", +- "@types/jest": "^26.0.7", +- "@types/node": "^14.14.32", ++ "@testing-library/react": "^12.1.3", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^8.0.0", + "@types/react-dom": "*", +- "cross-env": "^7.0.0", +- "cypress": "^7.3.0", +- "eslint-plugin-cypress": "^2.10.3", +- "start-server-and-test": "^1.10.11" +- }, +- "scripts": { +- "start": "backstage-cli app:serve", +- "build": "backstage-cli app:build", +- "clean": "backstage-cli clean", +- "test": "backstage-cli test", +- "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", +- "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", +- "lint": "backstage-cli lint", +- "cy:dev": "cypress open", +- "cy:run": "cypress run" ++ "cross-env": "^7.0.0" + }, +diff --git a/packages/app/public/index.html b/packages/app/public/index.html +index b93896c..fc758ee 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -10,3 +10,2 @@ + /> +- + +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 8a53583..8d62f29 100644 +--- a/packages/app/src/App.tsx ++++ b/packages/app/src/App.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { Navigate, Route } from 'react-router'; ++import { Navigate, Route } from 'react-router-dom'; + import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; +@@ -13,2 +13,3 @@ import { + import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; ++import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +@@ -16,3 +17,2 @@ import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -21,2 +21,4 @@ import { + } from '@backstage/plugin-techdocs'; ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; + import { UserSettingsPage } from '@backstage/plugin-user-settings'; +@@ -29,3 +31,6 @@ import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; + import { createApp } from '@backstage/app-defaults'; +-import { FlatRoutes } from '@backstage/core-app-api'; ++import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; ++import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; ++import { RequirePermission } from '@backstage/plugin-permission-react'; ++import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; + +@@ -37,5 +42,6 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); + bind(apiDocsPlugin.externalRoutes, { +- createComponent: scaffolderPlugin.routes.root, ++ registerApi: catalogImportPlugin.routes.importPage, + }); +@@ -43,2 +49,6 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, ++ }); ++ bind(orgPlugin.externalRoutes, { ++ catalogIndex: catalogPlugin.routes.catalogIndex, + }); +@@ -47,8 +57,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -60,5 +67,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -73,3 +82,10 @@ const routes = ( + /> +- } /> ++ ++ ++ ++ } ++ /> + }> +@@ -78,2 +94,3 @@ const routes = ( + } /> ++ } /> + +@@ -81,4 +98,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -88,5 +105,3 @@ const App = () => ( + +- ++ , + ); +- +-export default App; +diff --git a/packages/app/src/components/Root/LogoFull.tsx b/packages/app/src/components/Root/LogoFull.tsx +index c7b1c84..47e3b73 100644 +--- a/packages/app/src/components/Root/LogoFull.tsx ++++ b/packages/app/src/components/Root/LogoFull.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/LogoIcon.tsx b/packages/app/src/components/Root/LogoIcon.tsx +index 073cf6e..7eae8c7 100644 +--- a/packages/app/src/components/Root/LogoIcon.tsx ++++ b/packages/app/src/components/Root/LogoIcon.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx +index b4fa04f..6768b48 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,19 +1,3 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- +-import React, { useContext, PropsWithChildren } from 'react'; +-import { Link, makeStyles } from '@material-ui/core'; ++import React, { PropsWithChildren } from 'react'; ++import { makeStyles } from '@material-ui/core'; + import HomeIcon from '@material-ui/icons/Home'; +@@ -25,18 +9,21 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; +-import { Settings as SidebarSettings } from '@backstage/plugin-user-settings'; + import { +- SidebarSearchModal, +- SearchContextProvider, +-} from '@backstage/plugin-search'; ++ Settings as SidebarSettings, ++ UserSettingsSignInAvatar, ++} from '@backstage/plugin-user-settings'; ++import { SidebarSearchModal } from '@backstage/plugin-search'; + import { + Sidebar, +- SidebarPage, + sidebarConfig, +- SidebarContext, +- SidebarItem, + SidebarDivider, +- SidebarSpace, ++ SidebarGroup, ++ SidebarItem, ++ SidebarPage, + SidebarScrollWrapper, ++ SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; ++import MenuIcon from '@material-ui/icons/Menu'; ++import SearchIcon from '@material-ui/icons/Search'; + +@@ -59,3 +46,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -63,8 +50,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -79,19 +61,27 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ } to="/search"> + +- ++ + +- {/* Global nav, not org-specific */} +- +- +- +- +- {/* End global nav */} +- +- +- +- ++ }> ++ {/* Global nav, not org-specific */} ++ ++ ++ ++ ++ {/* End global nav */} ++ ++ ++ ++ ++ + + +- ++ } ++ to="/settings" ++ > ++ ++ + +diff --git a/packages/app/src/components/Root/index.ts b/packages/app/src/components/Root/index.ts +index dff706f..3528881 100644 +--- a/packages/app/src/components/Root/index.ts ++++ b/packages/app/src/components/Root/index.ts +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + export { Root } from './Root'; +diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx +index d3b4b78..6722ea2 100644 +--- a/packages/app/src/components/catalog/EntityPage.tsx ++++ b/packages/app/src/components/catalog/EntityPage.tsx +@@ -1,16 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ + import React from 'react'; +@@ -29,3 +14,2 @@ import { + EntityDependsOnResourcesCard, +- EntitySystemDiagramCard, + EntityHasComponentsCard, +@@ -43,2 +27,4 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +@@ -56,2 +42,27 @@ import { EntityTechdocsContent } from '@backstage/plugin-techdocs'; + import { EmptyState } from '@backstage/core-components'; ++import { ++ Direction, ++ EntityCatalogGraphCard, ++} from '@backstage/plugin-catalog-graph'; ++import { ++ RELATION_API_CONSUMED_BY, ++ RELATION_API_PROVIDED_BY, ++ RELATION_CONSUMES_API, ++ RELATION_DEPENDENCY_OF, ++ RELATION_DEPENDS_ON, ++ RELATION_HAS_PART, ++ RELATION_PART_OF, ++ RELATION_PROVIDES_API, ++} from '@backstage/catalog-model'; ++ ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); + +@@ -94,2 +105,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -110,2 +129,6 @@ const overviewContent = ( + ++ ++ ++ ++ + +@@ -152,3 +175,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -179,3 +202,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -198,3 +221,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -225,2 +248,5 @@ const apiPage = ( + ++ ++ ++ + +@@ -292,3 +318,9 @@ const systemPage = ( + +- ++ ++ ++ ++ ++ ++ ++ + +@@ -304,3 +336,19 @@ const systemPage = ( + +- ++ + +@@ -317,2 +365,5 @@ const domainPage = ( + ++ ++ ++ + +diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx +index 50ffbad..1788dde 100644 +--- a/packages/app/src/components/search/SearchPage.tsx ++++ b/packages/app/src/components/search/SearchPage.tsx +@@ -1,7 +1,12 @@ + import React from 'react'; +-import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; ++import { makeStyles, Theme, Grid, Paper } from '@material-ui/core'; + +-import { CatalogResultListItem } from '@backstage/plugin-catalog'; +-import { DocsResultListItem } from '@backstage/plugin-techdocs'; ++import { CatalogSearchResultListItem } from '@backstage/plugin-catalog'; ++import { ++ catalogApiRef, ++ CATALOG_FILTER_EXISTS, ++} from '@backstage/plugin-catalog-react'; ++import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; + ++import { SearchType } from '@backstage/plugin-search'; + import { +@@ -10,6 +15,13 @@ import { + SearchResult, +- SearchType, +- DefaultResultListItem, +-} from '@backstage/plugin-search'; +-import { Content, Header, Page } from '@backstage/core-components'; ++ SearchPagination, ++ useSearch, ++} from '@backstage/plugin-search-react'; ++import { ++ CatalogIcon, ++ Content, ++ DocsIcon, ++ Header, ++ Page, ++} from '@backstage/core-components'; ++import { useApi } from '@backstage/core-plugin-api'; + +@@ -21,2 +33,3 @@ const useStyles = makeStyles((theme: Theme) => ({ + padding: theme.spacing(2), ++ marginTop: theme.spacing(2), + }, +@@ -31,2 +44,4 @@ const SearchPage = () => { + const classes = useStyles(); ++ const { types } = useSearch(); ++ const catalogApi = useApi(catalogApiRef); + +@@ -39,3 +54,3 @@ const SearchPage = () => { + +- ++ + +@@ -43,10 +58,43 @@ const SearchPage = () => { + ++ , ++ }, ++ { ++ value: 'techdocs', ++ name: 'Documentation', ++ icon: , ++ }, ++ ]} ++ /> + +- ++ {types.includes('techdocs') && ( ++ { ++ // Return a list of entities which are documented. ++ const { items } = await catalogApi.getEntities({ ++ fields: ['metadata.name'], ++ filter: { ++ 'metadata.annotations.backstage.io/techdocs-ref': ++ CATALOG_FILTER_EXISTS, ++ }, ++ }); ++ ++ const names = items.map(entity => entity.metadata.name); ++ names.sort(); ++ return names; ++ }} ++ /> ++ )} + { + className={classes.filter} ++ label="Lifecycle" + name="lifecycle" +@@ -62,32 +111,6 @@ const SearchPage = () => { + ++ + +- {({ results }) => ( +- +- {results.map(({ type, document }) => { +- switch (type) { +- case 'software-catalog': +- return ( +- +- ); +- case 'techdocs': +- return ( +- +- ); +- default: +- return ( +- +- ); +- } +- })} +- +- )} ++ } /> ++ } /> + +diff --git a/packages/backend/.eslintrc.js b/packages/backend/.eslintrc.js +index 16a033d..e2a53a6 100644 +--- a/packages/backend/.eslintrc.js ++++ b/packages/backend/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint.backend')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index 31231a3..18548e9 100644 +--- a/packages/backend/Dockerfile ++++ b/packages/backend/Dockerfile +@@ -7,3 +7,3 @@ + # yarn tsc +-# yarn build ++# yarn build:backend + # +@@ -11,6 +11,29 @@ + +-FROM node:14-buster-slim ++FROM node:18-bookworm-slim + ++# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends python3 g++ build-essential && \ ++ yarn config set python /usr/bin/python3 ++ ++# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, ++# in which case you should also move better-sqlite3 to "devDependencies" in package.json. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends libsqlite3-dev ++ ++# From here on we use the least-privileged `node` user to run the backend. ++USER node ++ ++# This should create the app dir as `node`. ++# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. ++# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. + WORKDIR /app + ++# This switches many Node.js dependencies to production mode. ++ENV NODE_ENV production ++ + # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. +@@ -18,11 +41,12 @@ WORKDIR /app + # and along with yarn.lock and the root package.json, that's enough to run yarn install. +-COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ ++COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ + RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz + +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + + # Then copy the rest of the backend bundle, along with any other files we might want. +-COPY packages/backend/dist/bundle.tar.gz app-config.yaml ./ ++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"] +diff --git a/packages/backend/README.md b/packages/backend/README.md +index 02426ef..3607b0a 100644 +--- a/packages/backend/README.md ++++ b/packages/backend/README.md +@@ -18,4 +18,2 @@ To run the example backend, first go to the project root and run + yarn install +-yarn tsc +-yarn build + ``` +@@ -27,7 +25,2 @@ After that, go to the `packages/backend` directory and run + ```bash +-AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x \ +-AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x \ +-AUTH_OAUTH2_CLIENT_ID=x AUTH_OAUTH2_CLIENT_SECRET=x \ +-AUTH_OAUTH2_AUTH_URL=x AUTH_OAUTH2_TOKEN_URL=x \ +-LOG_LEVEL=debug \ + yarn start +@@ -35,4 +28,4 @@ yarn start + +-Substitute `x` for actual values, or leave them as dummy values just to try out +-the backend without using the auth or sentry features. ++If you want to override any configuration locally, for example adding any secrets, ++you can do so in `app-config.local.yaml`. + +@@ -45,3 +38,3 @@ locations to the backend. These are places where the backend can find some + entity descriptor data to consume and serve. For more information, see +-[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog). ++[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). + +@@ -65,2 +58,2 @@ and + - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +-- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) ++- [Backstage Documentation](https://backstage.io/docs) +diff --git a/packages/backend/package.json b/packages/backend/package.json +index 9129e5c..f224eaf 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,27 +6,33 @@ + "private": true, ++ "backstage": { ++ "role": "backend" ++ }, + "scripts": { +- "build": "backstage-cli backend:bundle", +- "build-image": "docker build ../.. -f Dockerfile --tag backstage", +- "start": "backstage-cli backend:dev", +- "lint": "backstage-cli lint", +- "test": "backstage-cli test", +- "clean": "backstage-cli clean", +- "migrate:create": "knex migrate:make -x ts" ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "lint": "backstage-cli package lint", ++ "test": "backstage-cli package test", ++ "clean": "backstage-cli package clean", ++ "build-image": "docker build ../.. -f Dockerfile --tag backstage" + }, + "dependencies": { +- "app": "0.0.0", +- "@backstage/backend-common": "^0.9.14", +- "@backstage/catalog-model": "^0.9.8", +- "@backstage/catalog-client": "^0.5.2", +- "@backstage/config": "^0.1.11", +- "@backstage/plugin-app-backend": "^0.3.19", +- "@backstage/plugin-auth-backend": "^0.5.1", +- "@backstage/plugin-catalog-backend": "^0.19.2", +- "@backstage/plugin-proxy-backend": "^0.2.14", +- "@backstage/plugin-scaffolder-backend": "^0.15.17", +- "@backstage/plugin-search-backend": "^0.2.8", +- "@backstage/plugin-search-backend-node": "^0.4.3", +- "@backstage/plugin-techdocs-backend": "^0.12.0", +- "@gitbeaker/node": "^34.6.0", +- "@octokit/rest": "^18.5.3", ++ "@backstage/backend-common": "^0.19.9-next.1", ++ "@backstage/backend-tasks": "^0.5.12-next.1", ++ "@backstage/catalog-client": "^1.4.5", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/config": "^1.1.1", ++ "@backstage/plugin-app-backend": "^0.3.55-next.1", ++ "@backstage/plugin-auth-backend": "^0.20.0-next.1", ++ "@backstage/plugin-auth-node": "^0.4.1-next.1", ++ "@backstage/plugin-catalog-backend": "^1.15.0-next.1", ++ "@backstage/plugin-permission-common": "^0.7.9", ++ "@backstage/plugin-permission-node": "^0.7.18-next.1", ++ "@backstage/plugin-proxy-backend": "^0.4.5-next.1", ++ "@backstage/plugin-scaffolder-backend": "^1.19.0-next.1", ++ "@backstage/plugin-search-backend": "^1.4.7-next.1", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.16-next.1", ++ "@backstage/plugin-search-backend-node": "^1.2.11-next.1", ++ "@backstage/plugin-techdocs-backend": "^1.9.0-next.1", ++ "app": "link:../app", ++ "better-sqlite3": "^8.0.0", + "dockerode": "^3.3.1", +@@ -34,4 +40,3 @@ + "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "pg": "^8.3.0", + "winston": "^3.2.1" +@@ -39,6 +44,7 @@ + "devDependencies": { +- "@backstage/cli": "^0.10.2", ++ "@backstage/cli": "^0.24.0-next.1", + "@types/dockerode": "^3.3.0", + "@types/express": "^4.17.6", +- "@types/express-serve-static-core": "^4.17.5" ++ "@types/express-serve-static-core": "^4.17.5", ++ "@types/luxon": "^2.0.4" + }, +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 3f12122..04c4ff9 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -17,3 +17,3 @@ import { + DatabaseManager, +- SingleHostDiscovery, ++ HostDiscovery, + UrlReaders, +@@ -21,2 +21,3 @@ import { + } from '@backstage/backend-common'; ++import { TaskScheduler } from '@backstage/backend-tasks'; + import { Config } from '@backstage/config'; +@@ -30,2 +31,4 @@ import search from './plugins/search'; + import { PluginEnvironment } from './types'; ++import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; + +@@ -34,9 +37,17 @@ function makeCreateEnv(config: Config) { + const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); +- +- root.info(`Created UrlReader ${reader}`); +- ++ const discovery = HostDiscovery.fromConfig(config); + const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++ const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); + const tokenManager = ServerTokenManager.noop(); ++ const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); ++ ++ const identity = DefaultIdentityClient.create({ ++ discovery, ++ }); ++ const permissions = ServerPermissionClient.fromConfig(config, { ++ discovery, ++ tokenManager, ++ }); ++ ++ root.info(`Created UrlReader ${reader}`); + +@@ -46,3 +57,15 @@ function makeCreateEnv(config: Config) { + const cache = cacheManager.forPlugin(plugin); +- return { logger, database, cache, config, reader, discovery, tokenManager }; ++ const scheduler = taskScheduler.forPlugin(plugin); ++ return { ++ logger, ++ database, ++ cache, ++ config, ++ reader, ++ discovery, ++ tokenManager, ++ scheduler, ++ permissions, ++ identity, ++ }; + }; +@@ -72,2 +95,4 @@ async function main() { + apiRouter.use('/search', await search(searchEnv)); ++ ++ // Add backends ABOVE this line; this 404 handler is the catch-all fallback + apiRouter.use(notFoundHandler()); +@@ -87,3 +112,3 @@ module.hot?.accept(); + main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); ++ console.error('Backend failed to start up', error); + process.exit(1); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +index 07fb04f..7c37f68 100644 +--- a/packages/backend/src/plugins/app.ts ++++ b/packages/backend/src/plugins/app.ts +@@ -4,9 +4,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + return await createRouter({ +- logger, +- config, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, + appPackageName: 'app', +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +index 5216510..77eb6aa 100644 +--- a/packages/backend/src/plugins/auth.ts ++++ b/packages/backend/src/plugins/auth.ts +@@ -1,2 +1,6 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; ++import { ++ createRouter, ++ providers, ++ defaultAuthProviderFactories, ++} from '@backstage/plugin-auth-backend'; + import { Router } from 'express'; +@@ -4,9 +8,47 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, database, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, ++ providerFactories: { ++ ...defaultAuthProviderFactories, ++ ++ // This replaces the default GitHub auth provider with a customized one. ++ // The `signIn` option enables sign-in for this provider, using the ++ // identity resolution logic that's provided in the `resolver` callback. ++ // ++ // This particular resolver makes all users share a single "guest" identity. ++ // It should only be used for testing and trying out Backstage. ++ // ++ // If you want to use a production ready resolver you can switch to ++ // the one that is commented out below, it looks up a user entity in the ++ // catalog using the GitHub username of the authenticated user. ++ // That resolver requires you to have user entities populated in the catalog, ++ // for example using https://backstage.io/docs/integrations/github/org ++ // ++ // There are other resolvers to choose from, and you can also create ++ // your own, see the auth documentation for more details: ++ // ++ // https://backstage.io/docs/auth/identity-resolver ++ github: providers.github.create({ ++ signIn: { ++ resolver(_, ctx) { ++ const userRef = 'user:default/guest'; // Must be a full entity reference ++ return ctx.issueToken({ ++ claims: { ++ sub: userRef, // The user's own identity ++ ent: [userRef], // A list of identities that the user claims ownership through ++ }, ++ }); ++ }, ++ // resolver: providers.github.resolvers.usernameMatchingUserEntityName(), ++ }, ++ }), ++ }, ++ }); + } +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +index 506f6d9..54ec393 100644 +--- a/packages/backend/src/plugins/proxy.ts ++++ b/packages/backend/src/plugins/proxy.ts +@@ -4,8 +4,10 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ }); + } +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +index 6be2e97..a12fee2 100644 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ b/packages/backend/src/plugins/scaffolder.ts +@@ -1,5 +1,3 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; + import { CatalogClient } from '@backstage/catalog-client'; + import { createRouter } from '@backstage/plugin-scaffolder-backend'; +-import Docker from 'dockerode'; + import { Router } from 'express'; +@@ -7,20 +5,17 @@ import type { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- database, +- reader, +- discovery, +-}: PluginEnvironment): Promise { +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- const catalogClient = new CatalogClient({ discoveryApi: discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ const catalogClient = new CatalogClient({ ++ discoveryApi: env.discovery, ++ }); + + return await createRouter({ +- containerRunner, +- logger, +- config, +- database, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ reader: env.reader, + catalogClient, +- reader, ++ identity: env.identity, ++ permissions: env.permissions, + }); +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +index f23b0c7..e9469dc 100644 +--- a/packages/backend/src/plugins/search.ts ++++ b/packages/backend/src/plugins/search.ts +@@ -7,14 +7,25 @@ import { + import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend'; ++import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend'; ++import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; ++import { Router } from 'express'; + +-export default async function createPlugin({ +- logger, +- discovery, +- config, +- tokenManager, +-}: PluginEnvironment) { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); ++ const searchEngine = new LunrSearchEngine({ ++ logger: env.logger, ++ }); ++ const indexBuilder = new IndexBuilder({ ++ logger: env.logger, ++ searchEngine, ++ }); ++ ++ const schedule = env.scheduler.createScheduledTaskRunner({ ++ frequency: { minutes: 10 }, ++ timeout: { minutes: 15 }, ++ // A 3 second delay gives the backend server a chance to initialize before ++ // any collators are executed, which may attempt requests against the API. ++ initialDelay: { seconds: 3 }, ++ }); + +@@ -23,6 +34,6 @@ export default async function createPlugin({ + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { +- discovery, +- tokenManager, ++ schedule, ++ factory: DefaultCatalogCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, + }), +@@ -32,7 +43,7 @@ export default async function createPlugin({ + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultTechDocsCollator.fromConfig(config, { +- discovery, +- logger, +- tokenManager, ++ schedule, ++ factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ logger: env.logger, ++ tokenManager: env.tokenManager, + }), +@@ -43,6 +54,4 @@ export default async function createPlugin({ + const { scheduler } = await indexBuilder.build(); ++ scheduler.start(); + +- // A 3 second delay gives the backend server a chance to initialize before +- // any collators are executed, which may attempt requests against the API. +- setTimeout(() => scheduler.start(), 3000); + useHotCleanup(module, () => scheduler.stop()); +@@ -51,3 +60,6 @@ export default async function createPlugin({ + engine: indexBuilder.getSearchEngine(), +- logger, ++ types: indexBuilder.getDocumentTypes(), ++ permissions: env.permissions, ++ config: env.config, ++ logger: env.logger, + }); +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +index 054c64d..be8bb0c 100644 +--- a/packages/backend/src/plugins/techdocs.ts ++++ b/packages/backend/src/plugins/techdocs.ts +@@ -11,13 +11,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +- cache, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, ++ const preparers = await Preparers.fromConfig(env.config, { ++ logger: env.logger, ++ reader: env.reader, + }); +@@ -29,4 +25,4 @@ export default async function createPlugin({ + // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, ++ const generators = await Generators.fromConfig(env.config, { ++ logger: env.logger, + containerRunner, +@@ -37,5 +33,5 @@ export default async function createPlugin({ + // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, ++ const publisher = await Publisher.fromConfig(env.config, { ++ logger: env.logger, ++ discovery: env.discovery, + }); +@@ -49,6 +45,6 @@ export default async function createPlugin({ + publisher, +- logger, +- config, +- discovery, +- cache, ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ cache: env.cache, + }); +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +index b1e2e0a..9cd2c74 100644 +--- a/packages/backend/src/types.ts ++++ b/packages/backend/src/types.ts +@@ -9,2 +9,5 @@ import { + } from '@backstage/backend-common'; ++import { PluginTaskScheduler } from '@backstage/backend-tasks'; ++import { PermissionEvaluator } from '@backstage/plugin-permission-common'; ++import { IdentityApi } from '@backstage/plugin-auth-node'; + +@@ -18,2 +21,5 @@ export type PluginEnvironment = { + tokenManager: TokenManager; ++ scheduler: PluginTaskScheduler; ++ permissions: PermissionEvaluator; ++ identity: IdentityApi; + }; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..37c7fb1 +--- /dev/null ++++ b/playwright.config.ts +@@ -0,0 +1,60 @@ ++/* ++ * Copyright 2023 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { defineConfig } from '@playwright/test'; ++import { generateProjects } from '@backstage/e2e-test-utils/playwright'; ++ ++/** ++ * See https://playwright.dev/docs/test-configuration. ++ */ ++export default defineConfig({ ++ timeout: 60_000, ++ ++ expect: { ++ timeout: 5_000, ++ }, ++ ++ // Run your local dev server before starting the tests ++ webServer: process.env.CI ++ ? [] ++ : [ ++ { ++ command: 'yarn start', ++ port: 3000, ++ reuseExistingServer: true, ++ timeout: 60_000, ++ }, ++ ], ++ ++ forbidOnly: !!process.env.CI, ++ ++ retries: process.env.CI ? 2 : 0, ++ ++ reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]], ++ ++ use: { ++ actionTimeout: 0, ++ baseURL: ++ process.env.PLAYWRIGHT_URL ?? ++ (process.env.CI ? 'http://localhost:7007' : 'http://localhost:3000'), ++ screenshot: 'only-on-failure', ++ trace: 'on-first-retry', ++ }, ++ ++ outputDir: 'node_modules/.cache/e2e-test-results', ++ ++ projects: generateProjects(), // Find all packages with e2e-test folders ++}); +diff --git a/plugins/README.md b/plugins/README.md +new file mode 100644 +index 0000000..d7865fd +--- /dev/null ++++ b/plugins/README.md +@@ -0,0 +1,9 @@ ++# The Plugins Folder ++ ++This is where your own plugins and their associated modules live, each in a ++separate folder of its own. ++ ++If you want to create a new plugin here, go to your project root directory, run ++the command `yarn new`, and follow the on-screen instructions. ++ ++You can also check out existing plugins on [the plugin marketplace](https://backstage.io/plugins)! +diff --git a/yarn.lock b/yarn.lock +new file mode 100644 +index 0000000..5ad7fe2 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1 @@ ++# intentionally left empty diff --git a/diffs/0.4.9..0.5.7-next.1.diff b/diffs/0.4.9..0.5.7-next.1.diff new file mode 100644 index 00000000..67c3f73c --- /dev/null +++ b/diffs/0.4.9..0.5.7-next.1.diff @@ -0,0 +1,1899 @@ +diff --git a/.dockerignore b/.dockerignore +index 63c9c34..05edb62 100644 +--- a/.dockerignore ++++ b/.dockerignore +@@ -1,5 +1,8 @@ + .git ++.yarn/cache ++.yarn/install-state.gz + node_modules +-packages +-!packages/backend/dist ++packages/*/src ++packages/*/node_modules + plugins ++*.local.yaml +diff --git a/.eslintignore b/.eslintignore +new file mode 100644 +index 0000000..e5b1994 +--- /dev/null ++++ b/.eslintignore +@@ -0,0 +1 @@ ++playwright.config.ts +diff --git a/.gitignore b/.gitignore +index d16a8d3..fbf8139 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -17,2 +17,11 @@ node_modules/ + ++# Yarn 3 files ++.pnp.* ++.yarn/* ++!.yarn/patches ++!.yarn/plugins ++!.yarn/releases ++!.yarn/sdks ++!.yarn/versions ++ + # Node version directives +@@ -39 +48,7 @@ site + *-credentials.yaml ++ ++# vscode database functionality support files ++*.session.sql ++ ++# E2E test reports ++e2e-test-report/ +diff --git a/app-config.production.yaml b/app-config.production.yaml +index 5e36c23..8f0751c 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -1,3 +1,3 @@ + app: +- # Should be the same as backend.baseUrl when using the `app-backend` plugin ++ # Should be the same as backend.baseUrl when using the `app-backend` plugin. + baseUrl: http://localhost:7007 +@@ -5,4 +5,31 @@ app: + backend: ++ # Note that the baseUrl should be the URL that the browser and other clients ++ # should use when communicating with the backend, i.e. it needs to be ++ # reachable not just from within the backend host, but from all of your ++ # callers. When its value is "http://localhost:7007", it's strictly private ++ # and can't be reached by others. + baseUrl: http://localhost:7007 +- listen: +- port: 7007 ++ # The listener can also be expressed as a single : string. In this case we bind to ++ # all interfaces, the most permissive setting. The right value depends on your specific deployment. ++ listen: ':7007' ++ ++ # config options: https://node-postgres.com/api/client ++ database: ++ client: pg ++ connection: ++ host: ${POSTGRES_HOST} ++ port: ${POSTGRES_PORT} ++ user: ${POSTGRES_USER} ++ password: ${POSTGRES_PASSWORD} ++ # https://node-postgres.com/features/ssl ++ # you can set the sslmode configuration option via the `PGSSLMODE` environment variable ++ # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require) ++ # ssl: ++ # ca: # if you have a CA file and want to verify it you can uncomment this section ++ # $file: /ca/server.crt ++ ++catalog: ++ # Overrides the default list locations from app-config.yaml as these contain example data. ++ # See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details ++ # on how to get entities into the catalog. ++ locations: [] +diff --git a/app-config.yaml b/app-config.yaml +index 1a622a2..08d2abf 100644 +--- a/app-config.yaml ++++ b/app-config.yaml +@@ -9,3 +9,4 @@ backend: + # Used for enabling authentication, secret is shared by all backend plugins +- # See backend-to-backend-auth.md in the docs for information on the format ++ # See https://backstage.io/docs/auth/service-to-service-auth for ++ # information on the format + # auth: +@@ -16,2 +17,4 @@ backend: + port: 7007 ++ # Uncomment the following host directive to bind to specific interfaces ++ # host: 127.0.0.1 + csp: +@@ -22,9 +25,9 @@ backend: + origin: http://localhost:3000 +- methods: [GET, POST, PUT, DELETE] ++ methods: [GET, HEAD, PATCH, POST, PUT, DELETE] + credentials: true ++ # This is for local development only, it is not recommended to use this in production ++ # The production database configuration is stored in app-config.production.yaml + database: +- client: sqlite3 ++ client: better-sqlite3 + connection: ':memory:' +- cache: +- store: memory + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir +@@ -34,2 +37,4 @@ integrations: + - host: github.com ++ # This is a Personal Access Token or PAT from GitHub. You can find out how to generate this token, and more information ++ # about setting up the GitHub integration here: https://backstage.io/docs/getting-started/configuration#setting-up-a-github-integration + token: ${GITHUB_TOKEN} +@@ -41,5 +46,8 @@ integrations: + proxy: +- '/test': +- target: 'https://example.com' +- changeOrigin: true ++ ### Example for how to add a proxy endpoint for the frontend. ++ ### A typical reason to do this is to handle HTTPS and CORS for internal services. ++ # endpoints: ++ # '/test': ++ # target: 'https://example.com' ++ # changeOrigin: true + +@@ -64,41 +72,32 @@ scaffolder: + catalog: ++ import: ++ entityFilename: catalog-info.yaml ++ pullRequestBranchName: backstage-integration + rules: +- - allow: [Component, System, API, Group, User, Resource, Location] ++ - allow: [Component, System, API, Resource, Location] + locations: +- # Backstage example components +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-components.yaml ++ # Local example data, file locations are relative to the backend process, typically `packages/backend` ++ - type: file ++ target: ../../examples/entities.yaml + +- # Backstage example systems +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-systems.yaml +- +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml +- +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml +- +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.yaml +- +- # Backstage example templates +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/react-ssr-template/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/springboot-grpc-template/template.yaml ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml + rules: + - allow: [Template] +- - type: url +- target: https://github.com/spotify/cookiecutter-golang/blob/master/template.yaml +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/docs-template/template.yaml ++ ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml + rules: +- - allow: [Template] ++ - allow: [User, Group] ++ ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml ++ ++ ## Uncomment these lines to add an example org ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme-corp.yaml ++ # rules: ++ # - allow: [User, Group] +diff --git a/backstage.json b/backstage.json +index b565e30..707af29 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "0.4.9" ++ "version": "1.20.0-next.1" + } +diff --git a/examples/entities.yaml b/examples/entities.yaml +new file mode 100644 +index 0000000..447e8b1 +--- /dev/null ++++ b/examples/entities.yaml +@@ -0,0 +1,41 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system ++apiVersion: backstage.io/v1alpha1 ++kind: System ++metadata: ++ name: examples ++spec: ++ owner: guests ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: example-website ++spec: ++ type: website ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ providesApis: [example-grpc-api] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api ++apiVersion: backstage.io/v1alpha1 ++kind: API ++metadata: ++ name: example-grpc-api ++spec: ++ type: grpc ++ lifecycle: experimental ++ owner: guests ++ system: examples ++ definition: | ++ syntax = "proto3"; ++ ++ service Exampler { ++ rpc Example (ExampleMessage) returns (ExampleMessage) {}; ++ } ++ ++ message ExampleMessage { ++ string example = 1; ++ }; +diff --git a/examples/org.yaml b/examples/org.yaml +new file mode 100644 +index 0000000..a10e81f +--- /dev/null ++++ b/examples/org.yaml +@@ -0,0 +1,17 @@ ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user ++apiVersion: backstage.io/v1alpha1 ++kind: User ++metadata: ++ name: guest ++spec: ++ memberOf: [guests] ++--- ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group ++apiVersion: backstage.io/v1alpha1 ++kind: Group ++metadata: ++ name: guests ++spec: ++ type: team ++ children: [] +diff --git a/examples/template/content/catalog-info.yaml b/examples/template/content/catalog-info.yaml +new file mode 100644 +index 0000000..d4ccca4 +--- /dev/null ++++ b/examples/template/content/catalog-info.yaml +@@ -0,0 +1,8 @@ ++apiVersion: backstage.io/v1alpha1 ++kind: Component ++metadata: ++ name: ${{ values.name | dump }} ++spec: ++ type: service ++ owner: user:guest ++ lifecycle: experimental +diff --git a/examples/template/content/index.js b/examples/template/content/index.js +new file mode 100644 +index 0000000..071ce5a +--- /dev/null ++++ b/examples/template/content/index.js +@@ -0,0 +1 @@ ++console.log('Hello from ${{ values.name }}!'); +diff --git a/examples/template/content/package.json b/examples/template/content/package.json +new file mode 100644 +index 0000000..86f968a +--- /dev/null ++++ b/examples/template/content/package.json +@@ -0,0 +1,5 @@ ++{ ++ "name": "${{ values.name }}", ++ "private": true, ++ "dependencies": {} ++} +diff --git a/examples/template/template.yaml b/examples/template/template.yaml +new file mode 100644 +index 0000000..33f262b +--- /dev/null ++++ b/examples/template/template.yaml +@@ -0,0 +1,74 @@ ++apiVersion: scaffolder.backstage.io/v1beta3 ++# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template ++kind: Template ++metadata: ++ name: example-nodejs-template ++ title: Example Node.js Template ++ description: An example template for the scaffolder that creates a simple Node.js service ++spec: ++ owner: user:guest ++ type: service ++ ++ # These parameters are used to generate the input form in the frontend, and are ++ # used to gather input data for the execution of the template. ++ parameters: ++ - title: Fill in some steps ++ required: ++ - name ++ properties: ++ name: ++ title: Name ++ type: string ++ description: Unique name of the component ++ ui:autofocus: true ++ ui:options: ++ rows: 5 ++ - title: Choose a location ++ required: ++ - repoUrl ++ properties: ++ repoUrl: ++ title: Repository Location ++ type: string ++ ui:field: RepoUrlPicker ++ ui:options: ++ allowedHosts: ++ - github.com ++ ++ # These steps are executed in the scaffolder backend, using data that we gathered ++ # via the parameters above. ++ steps: ++ # Each step executes an action, in this case one templates files into the working directory. ++ - id: fetch-base ++ name: Fetch Base ++ action: fetch:template ++ input: ++ url: ./content ++ values: ++ name: ${{ parameters.name }} ++ ++ # This step publishes the contents of the working directory to GitHub. ++ - id: publish ++ name: Publish ++ action: publish:github ++ input: ++ allowedHosts: ['github.com'] ++ description: This is ${{ parameters.name }} ++ repoUrl: ${{ parameters.repoUrl }} ++ ++ # The final step is to register our new component in the catalog. ++ - id: register ++ name: Register ++ action: catalog:register ++ input: ++ repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} ++ catalogInfoPath: '/catalog-info.yaml' ++ ++ # Outputs are displayed to the user after a successful execution of the template. ++ output: ++ links: ++ - title: Repository ++ url: ${{ steps['publish'].output.remoteUrl }} ++ - title: Open in catalog ++ icon: catalog ++ entityRef: ${{ steps['register'].output.entityRef }} +diff --git a/lerna.json b/lerna.json +index 322929d..529a62f 100644 +--- a/lerna.json ++++ b/lerna.json +@@ -3,4 +3,4 @@ + "npmClient": "yarn", +- "useWorkspaces": true, +- "version": "0.1.0" ++ "version": "0.1.0", ++ "$schema": "node_modules/lerna/schemas/lerna-schema.json" + } +diff --git a/package.json b/package.json +index 564d0d1..2364da8 100644 +--- a/package.json ++++ b/package.json +@@ -5,3 +5,3 @@ + "engines": { +- "node": "14 || 16" ++ "node": "18 || 20" + }, +@@ -11,3 +11,4 @@ + "start-backend": "yarn workspace backend start", +- "build": "lerna run build", ++ "build:backend": "yarn workspace backend build", ++ "build:all": "backstage-cli repo build --all", + "build-image": "yarn workspace backend build-image", +@@ -15,11 +16,11 @@ + "tsc:full": "tsc --skipLibCheck false --incremental false", +- "clean": "backstage-cli clean && lerna run clean", +- "diff": "lerna run diff --", +- "test": "backstage-cli test", +- "test:all": "lerna run test -- --coverage", +- "lint": "lerna run lint --since origin/master --", +- "lint:all": "lerna run lint --", ++ "clean": "backstage-cli repo clean", ++ "test": "backstage-cli repo test", ++ "test:all": "backstage-cli repo test --coverage", ++ "test:e2e": "playwright test", ++ "fix": "backstage-cli repo fix", ++ "lint": "backstage-cli repo lint --since origin/master", ++ "lint:all": "backstage-cli repo lint", + "prettier:check": "prettier --check .", +- "create-plugin": "backstage-cli create-plugin --scope internal", +- "remove-plugin": "backstage-cli remove-plugin" ++ "new": "backstage-cli new --scope internal" + }, +@@ -32,7 +33,15 @@ + "devDependencies": { +- "@backstage/cli": "^0.10.3", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/e2e-test-utils": "^0.1.0", ++ "@playwright/test": "^1.32.3", + "@spotify/prettier-config": "^12.0.0", +- "concurrently": "^6.0.0", +- "lerna": "^4.0.0", +- "prettier": "^2.3.2" ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^9.0.0", ++ "prettier": "^2.3.2", ++ "typescript": "~5.2.0" ++ }, ++ "resolutions": { ++ "@types/react": "^17", ++ "@types/react-dom": "^17" + }, +diff --git a/packages/README.md b/packages/README.md +new file mode 100644 +index 0000000..6327fa0 +--- /dev/null ++++ b/packages/README.md +@@ -0,0 +1,9 @@ ++# The Packages Folder ++ ++This is where your own applications and centrally managed libraries live, each ++in a separate folder of its own. ++ ++From the start there's an `app` folder (for the frontend) and a `backend` folder ++(for the Node backend), but you can also add more modules in here that house ++your core additions and adaptations, such as themes, common React component ++libraries, utilities, and similar. +diff --git a/packages/app/.eslintignore b/packages/app/.eslintignore +new file mode 100644 +index 0000000..a48cf0d +--- /dev/null ++++ b/packages/app/.eslintignore +@@ -0,0 +1 @@ ++public +diff --git a/packages/app/.eslintrc.js b/packages/app/.eslintrc.js +index 13573ef..e2a53a6 100644 +--- a/packages/app/.eslintrc.js ++++ b/packages/app/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/app/cypress.json b/packages/app/cypress.json +deleted file mode 100644 +index 5de7ebf..0000000 +--- a/packages/app/cypress.json ++++ /dev/null +@@ -1,5 +0,0 @@ +-{ +- "baseUrl": "http://localhost:3001", +- "fixturesFolder": false, +- "pluginsFile": false +-} +diff --git a/packages/app/cypress/.eslintrc.json b/packages/app/cypress/.eslintrc.json +deleted file mode 100644 +index 2b3a458..0000000 +--- a/packages/app/cypress/.eslintrc.json ++++ /dev/null +@@ -1,21 +0,0 @@ +-{ +- "plugins": ["cypress"], +- "extends": ["plugin:cypress/recommended"], +- "rules": { +- "jest/expect-expect": [ +- "error", +- { +- "assertFunctionNames": ["expect", "cy.contains"] +- } +- ], +- "import/no-extraneous-dependencies": [ +- "error", +- { +- "devDependencies": true, +- "optionalDependencies": true, +- "peerDependencies": true, +- "bundledDependencies": true +- } +- ] +- } +-} +diff --git a/packages/app/cypress/integration/app.js b/packages/app/cypress/integration/app.js +deleted file mode 100644 +index 43fb2e3..0000000 +--- a/packages/app/cypress/integration/app.js ++++ /dev/null +@@ -1,6 +0,0 @@ +-describe('App', () => { +- it('should render the catalog', () => { +- cy.visit('/'); +- cy.contains('My Company Catalog'); +- }); +-}); +diff --git a/packages/app/e2e-tests/app.test.ts b/packages/app/e2e-tests/app.test.ts +new file mode 100644 +index 0000000..d45bc0d +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,23 @@ ++/* ++ * Copyright 2020 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { test, expect } from '@playwright/test'; ++ ++test('App should render the welcome page', async ({ page }) => { ++ await page.goto('/'); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index ba91715..2100405 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,22 +5,38 @@ + "bundled": true, ++ "backstage": { ++ "role": "frontend" ++ }, ++ "scripts": { ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "clean": "backstage-cli package clean", ++ "test": "backstage-cli package test", ++ "lint": "backstage-cli package lint" ++ }, + "dependencies": { +- "@backstage/app-defaults": "^0.1.3", +- "@backstage/catalog-model": "^0.9.8", +- "@backstage/cli": "^0.10.3", +- "@backstage/core-app-api": "^0.3.0", +- "@backstage/core-components": "^0.8.2", +- "@backstage/core-plugin-api": "^0.4.0", +- "@backstage/integration-react": "^0.1.16", +- "@backstage/plugin-api-docs": "^0.6.19", +- "@backstage/plugin-catalog": "^0.7.5", +- "@backstage/plugin-catalog-import": "^0.7.6", +- "@backstage/plugin-catalog-react": "^0.6.8", +- "@backstage/plugin-github-actions": "^0.4.28", +- "@backstage/plugin-org": "^0.3.32", +- "@backstage/plugin-scaffolder": "^0.11.15", +- "@backstage/plugin-search": "^0.5.2", +- "@backstage/plugin-tech-radar": "^0.5.0", +- "@backstage/plugin-techdocs": "^0.12.11", +- "@backstage/plugin-user-settings": "^0.3.14", +- "@backstage/theme": "^0.2.14", ++ "@backstage/app-defaults": "^1.4.5-next.1", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/core-app-api": "^1.11.1-next.0", ++ "@backstage/core-components": "^0.13.8-next.1", ++ "@backstage/core-plugin-api": "^1.8.0-next.0", ++ "@backstage/integration-react": "^1.1.21-next.1", ++ "@backstage/plugin-api-docs": "^0.9.13-next.1", ++ "@backstage/plugin-catalog": "^1.15.0-next.1", ++ "@backstage/plugin-catalog-common": "^1.0.17", ++ "@backstage/plugin-catalog-graph": "^0.2.38-next.1", ++ "@backstage/plugin-catalog-import": "^0.10.2-next.1", ++ "@backstage/plugin-catalog-react": "^1.9.0-next.1", ++ "@backstage/plugin-github-actions": "^0.6.7-next.1", ++ "@backstage/plugin-org": "^0.6.16-next.1", ++ "@backstage/plugin-permission-react": "^0.4.17-next.0", ++ "@backstage/plugin-scaffolder": "^1.16.0-next.1", ++ "@backstage/plugin-search": "^1.4.2-next.1", ++ "@backstage/plugin-search-react": "^1.7.2-next.1", ++ "@backstage/plugin-tech-radar": "^0.6.10-next.1", ++ "@backstage/plugin-techdocs": "^1.8.1-next.1", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.2-next.1", ++ "@backstage/plugin-techdocs-react": "^1.1.13-next.1", ++ "@backstage/plugin-user-settings": "^0.7.12-next.1", ++ "@backstage/theme": "^0.4.4-next.0", + "@material-ui/core": "^4.12.2", +@@ -28,31 +44,17 @@ + "history": "^5.0.0", +- "react": "^16.13.1", +- "react-dom": "^16.13.1", +- "react-router": "6.0.0-beta.0", +- "react-router-dom": "6.0.0-beta.0", +- "react-use": "^15.3.3" ++ "react": "^17.0.2", ++ "react-dom": "^17.0.2", ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^0.2.0", ++ "@backstage/test-utils": "^1.4.5-next.0", ++ "@playwright/test": "^1.32.3", + "@testing-library/jest-dom": "^5.10.1", +- "@testing-library/react": "^10.4.1", +- "@testing-library/user-event": "^12.0.7", +- "@types/jest": "^26.0.7", +- "@types/node": "^14.14.32", ++ "@testing-library/react": "^12.1.3", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^8.0.0", + "@types/react-dom": "*", +- "cross-env": "^7.0.0", +- "cypress": "^7.3.0", +- "eslint-plugin-cypress": "^2.10.3", +- "start-server-and-test": "^1.10.11" +- }, +- "scripts": { +- "start": "backstage-cli app:serve", +- "build": "backstage-cli app:build", +- "clean": "backstage-cli clean", +- "test": "backstage-cli test", +- "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", +- "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", +- "lint": "backstage-cli lint", +- "cy:dev": "cypress open", +- "cy:run": "cypress run" ++ "cross-env": "^7.0.0" + }, +diff --git a/packages/app/public/index.html b/packages/app/public/index.html +index 1bd6001..fc758ee 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -10,3 +10,2 @@ + /> +- + +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 8a53583..8d62f29 100644 +--- a/packages/app/src/App.tsx ++++ b/packages/app/src/App.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { Navigate, Route } from 'react-router'; ++import { Navigate, Route } from 'react-router-dom'; + import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; +@@ -13,2 +13,3 @@ import { + import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; ++import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +@@ -16,3 +17,2 @@ import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -21,2 +21,4 @@ import { + } from '@backstage/plugin-techdocs'; ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; + import { UserSettingsPage } from '@backstage/plugin-user-settings'; +@@ -29,3 +31,6 @@ import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; + import { createApp } from '@backstage/app-defaults'; +-import { FlatRoutes } from '@backstage/core-app-api'; ++import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; ++import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; ++import { RequirePermission } from '@backstage/plugin-permission-react'; ++import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; + +@@ -37,5 +42,6 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); + bind(apiDocsPlugin.externalRoutes, { +- createComponent: scaffolderPlugin.routes.root, ++ registerApi: catalogImportPlugin.routes.importPage, + }); +@@ -43,2 +49,6 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, ++ }); ++ bind(orgPlugin.externalRoutes, { ++ catalogIndex: catalogPlugin.routes.catalogIndex, + }); +@@ -47,8 +57,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -60,5 +67,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -73,3 +82,10 @@ const routes = ( + /> +- } /> ++ ++ ++ ++ } ++ /> + }> +@@ -78,2 +94,3 @@ const routes = ( + } /> ++ } /> + +@@ -81,4 +98,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -88,5 +105,3 @@ const App = () => ( + +- ++ , + ); +- +-export default App; +diff --git a/packages/app/src/components/Root/LogoFull.tsx b/packages/app/src/components/Root/LogoFull.tsx +index c7b1c84..47e3b73 100644 +--- a/packages/app/src/components/Root/LogoFull.tsx ++++ b/packages/app/src/components/Root/LogoFull.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/LogoIcon.tsx b/packages/app/src/components/Root/LogoIcon.tsx +index 073cf6e..7eae8c7 100644 +--- a/packages/app/src/components/Root/LogoIcon.tsx ++++ b/packages/app/src/components/Root/LogoIcon.tsx +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + import React from 'react'; +diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx +index b4fa04f..6768b48 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,19 +1,3 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- +-import React, { useContext, PropsWithChildren } from 'react'; +-import { Link, makeStyles } from '@material-ui/core'; ++import React, { PropsWithChildren } from 'react'; ++import { makeStyles } from '@material-ui/core'; + import HomeIcon from '@material-ui/icons/Home'; +@@ -25,18 +9,21 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; +-import { Settings as SidebarSettings } from '@backstage/plugin-user-settings'; + import { +- SidebarSearchModal, +- SearchContextProvider, +-} from '@backstage/plugin-search'; ++ Settings as SidebarSettings, ++ UserSettingsSignInAvatar, ++} from '@backstage/plugin-user-settings'; ++import { SidebarSearchModal } from '@backstage/plugin-search'; + import { + Sidebar, +- SidebarPage, + sidebarConfig, +- SidebarContext, +- SidebarItem, + SidebarDivider, +- SidebarSpace, ++ SidebarGroup, ++ SidebarItem, ++ SidebarPage, + SidebarScrollWrapper, ++ SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; ++import MenuIcon from '@material-ui/icons/Menu'; ++import SearchIcon from '@material-ui/icons/Search'; + +@@ -59,3 +46,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -63,8 +50,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -79,19 +61,27 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ } to="/search"> + +- ++ + +- {/* Global nav, not org-specific */} +- +- +- +- +- {/* End global nav */} +- +- +- +- ++ }> ++ {/* Global nav, not org-specific */} ++ ++ ++ ++ ++ {/* End global nav */} ++ ++ ++ ++ ++ + + +- ++ } ++ to="/settings" ++ > ++ ++ + +diff --git a/packages/app/src/components/Root/index.ts b/packages/app/src/components/Root/index.ts +index dff706f..3528881 100644 +--- a/packages/app/src/components/Root/index.ts ++++ b/packages/app/src/components/Root/index.ts +@@ -1,17 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ +- + export { Root } from './Root'; +diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx +index d3b4b78..6722ea2 100644 +--- a/packages/app/src/components/catalog/EntityPage.tsx ++++ b/packages/app/src/components/catalog/EntityPage.tsx +@@ -1,16 +1 @@ +-/* +- * Copyright 2020 The Backstage Authors +- * +- * Licensed under the Apache License, Version 2.0 (the "License"); +- * you may not use this file except in compliance with the License. +- * You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, software +- * distributed under the License is distributed on an "AS IS" BASIS, +- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +- * See the License for the specific language governing permissions and +- * limitations under the License. +- */ + import React from 'react'; +@@ -29,3 +14,2 @@ import { + EntityDependsOnResourcesCard, +- EntitySystemDiagramCard, + EntityHasComponentsCard, +@@ -43,2 +27,4 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +@@ -56,2 +42,27 @@ import { EntityTechdocsContent } from '@backstage/plugin-techdocs'; + import { EmptyState } from '@backstage/core-components'; ++import { ++ Direction, ++ EntityCatalogGraphCard, ++} from '@backstage/plugin-catalog-graph'; ++import { ++ RELATION_API_CONSUMED_BY, ++ RELATION_API_PROVIDED_BY, ++ RELATION_CONSUMES_API, ++ RELATION_DEPENDENCY_OF, ++ RELATION_DEPENDS_ON, ++ RELATION_HAS_PART, ++ RELATION_PART_OF, ++ RELATION_PROVIDES_API, ++} from '@backstage/catalog-model'; ++ ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); + +@@ -94,2 +105,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -110,2 +129,6 @@ const overviewContent = ( + ++ ++ ++ ++ + +@@ -152,3 +175,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -179,3 +202,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -198,3 +221,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -225,2 +248,5 @@ const apiPage = ( + ++ ++ ++ + +@@ -292,3 +318,9 @@ const systemPage = ( + +- ++ ++ ++ ++ ++ ++ ++ + +@@ -304,3 +336,19 @@ const systemPage = ( + +- ++ + +@@ -317,2 +365,5 @@ const domainPage = ( + ++ ++ ++ + +diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx +index 95c8c64..1788dde 100644 +--- a/packages/app/src/components/search/SearchPage.tsx ++++ b/packages/app/src/components/search/SearchPage.tsx +@@ -1,7 +1,12 @@ + import React from 'react'; +-import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; ++import { makeStyles, Theme, Grid, Paper } from '@material-ui/core'; + +-import { CatalogResultListItem } from '@backstage/plugin-catalog'; +-import { DocsResultListItem } from '@backstage/plugin-techdocs'; ++import { CatalogSearchResultListItem } from '@backstage/plugin-catalog'; ++import { ++ catalogApiRef, ++ CATALOG_FILTER_EXISTS, ++} from '@backstage/plugin-catalog-react'; ++import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; + ++import { SearchType } from '@backstage/plugin-search'; + import { +@@ -10,6 +15,13 @@ import { + SearchResult, +- SearchType, +- DefaultResultListItem, +-} from '@backstage/plugin-search'; +-import { Content, Header, Page } from '@backstage/core-components'; ++ SearchPagination, ++ useSearch, ++} from '@backstage/plugin-search-react'; ++import { ++ CatalogIcon, ++ Content, ++ DocsIcon, ++ Header, ++ Page, ++} from '@backstage/core-components'; ++import { useApi } from '@backstage/core-plugin-api'; + +@@ -21,2 +33,3 @@ const useStyles = makeStyles((theme: Theme) => ({ + padding: theme.spacing(2), ++ marginTop: theme.spacing(2), + }, +@@ -31,2 +44,4 @@ const SearchPage = () => { + const classes = useStyles(); ++ const { types } = useSearch(); ++ const catalogApi = useApi(catalogApiRef); + +@@ -43,10 +58,43 @@ const SearchPage = () => { + ++ , ++ }, ++ { ++ value: 'techdocs', ++ name: 'Documentation', ++ icon: , ++ }, ++ ]} ++ /> + +- ++ {types.includes('techdocs') && ( ++ { ++ // Return a list of entities which are documented. ++ const { items } = await catalogApi.getEntities({ ++ fields: ['metadata.name'], ++ filter: { ++ 'metadata.annotations.backstage.io/techdocs-ref': ++ CATALOG_FILTER_EXISTS, ++ }, ++ }); ++ ++ const names = items.map(entity => entity.metadata.name); ++ names.sort(); ++ return names; ++ }} ++ /> ++ )} + { + className={classes.filter} ++ label="Lifecycle" + name="lifecycle" +@@ -62,32 +111,6 @@ const SearchPage = () => { + ++ + +- {({ results }) => ( +- +- {results.map(({ type, document }) => { +- switch (type) { +- case 'software-catalog': +- return ( +- +- ); +- case 'techdocs': +- return ( +- +- ); +- default: +- return ( +- +- ); +- } +- })} +- +- )} ++ } /> ++ } /> + +diff --git a/packages/backend/.eslintrc.js b/packages/backend/.eslintrc.js +index 16a033d..e2a53a6 100644 +--- a/packages/backend/.eslintrc.js ++++ b/packages/backend/.eslintrc.js +@@ -1,3 +1 @@ +-module.exports = { +- extends: [require.resolve('@backstage/cli/config/eslint.backend')], +-}; ++module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index 31231a3..18548e9 100644 +--- a/packages/backend/Dockerfile ++++ b/packages/backend/Dockerfile +@@ -7,3 +7,3 @@ + # yarn tsc +-# yarn build ++# yarn build:backend + # +@@ -11,6 +11,29 @@ + +-FROM node:14-buster-slim ++FROM node:18-bookworm-slim + ++# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends python3 g++ build-essential && \ ++ yarn config set python /usr/bin/python3 ++ ++# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, ++# in which case you should also move better-sqlite3 to "devDependencies" in package.json. ++RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ ++ --mount=type=cache,target=/var/lib/apt,sharing=locked \ ++ apt-get update && \ ++ apt-get install -y --no-install-recommends libsqlite3-dev ++ ++# From here on we use the least-privileged `node` user to run the backend. ++USER node ++ ++# This should create the app dir as `node`. ++# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. ++# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. + WORKDIR /app + ++# This switches many Node.js dependencies to production mode. ++ENV NODE_ENV production ++ + # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. +@@ -18,11 +41,12 @@ WORKDIR /app + # and along with yarn.lock and the root package.json, that's enough to run yarn install. +-COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ ++COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ + RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz + +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + + # Then copy the rest of the backend bundle, along with any other files we might want. +-COPY packages/backend/dist/bundle.tar.gz app-config.yaml ./ ++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"] +diff --git a/packages/backend/README.md b/packages/backend/README.md +index 02426ef..3607b0a 100644 +--- a/packages/backend/README.md ++++ b/packages/backend/README.md +@@ -18,4 +18,2 @@ To run the example backend, first go to the project root and run + yarn install +-yarn tsc +-yarn build + ``` +@@ -27,7 +25,2 @@ After that, go to the `packages/backend` directory and run + ```bash +-AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x \ +-AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x \ +-AUTH_OAUTH2_CLIENT_ID=x AUTH_OAUTH2_CLIENT_SECRET=x \ +-AUTH_OAUTH2_AUTH_URL=x AUTH_OAUTH2_TOKEN_URL=x \ +-LOG_LEVEL=debug \ + yarn start +@@ -35,4 +28,4 @@ yarn start + +-Substitute `x` for actual values, or leave them as dummy values just to try out +-the backend without using the auth or sentry features. ++If you want to override any configuration locally, for example adding any secrets, ++you can do so in `app-config.local.yaml`. + +@@ -45,3 +38,3 @@ locations to the backend. These are places where the backend can find some + entity descriptor data to consume and serve. For more information, see +-[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog). ++[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). + +@@ -65,2 +58,2 @@ and + - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +-- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) ++- [Backstage Documentation](https://backstage.io/docs) +diff --git a/packages/backend/package.json b/packages/backend/package.json +index 330a913..f224eaf 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,27 +6,33 @@ + "private": true, ++ "backstage": { ++ "role": "backend" ++ }, + "scripts": { +- "build": "backstage-cli backend:bundle", +- "build-image": "docker build ../.. -f Dockerfile --tag backstage", +- "start": "backstage-cli backend:dev", +- "lint": "backstage-cli lint", +- "test": "backstage-cli test", +- "clean": "backstage-cli clean", +- "migrate:create": "knex migrate:make -x ts" ++ "start": "backstage-cli package start", ++ "build": "backstage-cli package build", ++ "lint": "backstage-cli package lint", ++ "test": "backstage-cli package test", ++ "clean": "backstage-cli package clean", ++ "build-image": "docker build ../.. -f Dockerfile --tag backstage" + }, + "dependencies": { +- "app": "0.0.0", +- "@backstage/backend-common": "^0.10.0", +- "@backstage/catalog-model": "^0.9.8", +- "@backstage/catalog-client": "^0.5.3", +- "@backstage/config": "^0.1.11", +- "@backstage/plugin-app-backend": "^0.3.20", +- "@backstage/plugin-auth-backend": "^0.5.2", +- "@backstage/plugin-catalog-backend": "^0.19.3", +- "@backstage/plugin-proxy-backend": "^0.2.15", +- "@backstage/plugin-scaffolder-backend": "^0.15.18", +- "@backstage/plugin-search-backend": "^0.3.0", +- "@backstage/plugin-search-backend-node": "^0.4.3", +- "@backstage/plugin-techdocs-backend": "^0.12.1", +- "@gitbeaker/node": "^34.6.0", +- "@octokit/rest": "^18.5.3", ++ "@backstage/backend-common": "^0.19.9-next.1", ++ "@backstage/backend-tasks": "^0.5.12-next.1", ++ "@backstage/catalog-client": "^1.4.5", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/config": "^1.1.1", ++ "@backstage/plugin-app-backend": "^0.3.55-next.1", ++ "@backstage/plugin-auth-backend": "^0.20.0-next.1", ++ "@backstage/plugin-auth-node": "^0.4.1-next.1", ++ "@backstage/plugin-catalog-backend": "^1.15.0-next.1", ++ "@backstage/plugin-permission-common": "^0.7.9", ++ "@backstage/plugin-permission-node": "^0.7.18-next.1", ++ "@backstage/plugin-proxy-backend": "^0.4.5-next.1", ++ "@backstage/plugin-scaffolder-backend": "^1.19.0-next.1", ++ "@backstage/plugin-search-backend": "^1.4.7-next.1", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.16-next.1", ++ "@backstage/plugin-search-backend-node": "^1.2.11-next.1", ++ "@backstage/plugin-techdocs-backend": "^1.9.0-next.1", ++ "app": "link:../app", ++ "better-sqlite3": "^8.0.0", + "dockerode": "^3.3.1", +@@ -34,4 +40,3 @@ + "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "pg": "^8.3.0", + "winston": "^3.2.1" +@@ -39,6 +44,7 @@ + "devDependencies": { +- "@backstage/cli": "^0.10.3", ++ "@backstage/cli": "^0.24.0-next.1", + "@types/dockerode": "^3.3.0", + "@types/express": "^4.17.6", +- "@types/express-serve-static-core": "^4.17.5" ++ "@types/express-serve-static-core": "^4.17.5", ++ "@types/luxon": "^2.0.4" + }, +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 3f12122..04c4ff9 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -17,3 +17,3 @@ import { + DatabaseManager, +- SingleHostDiscovery, ++ HostDiscovery, + UrlReaders, +@@ -21,2 +21,3 @@ import { + } from '@backstage/backend-common'; ++import { TaskScheduler } from '@backstage/backend-tasks'; + import { Config } from '@backstage/config'; +@@ -30,2 +31,4 @@ import search from './plugins/search'; + import { PluginEnvironment } from './types'; ++import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; + +@@ -34,9 +37,17 @@ function makeCreateEnv(config: Config) { + const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); +- +- root.info(`Created UrlReader ${reader}`); +- ++ const discovery = HostDiscovery.fromConfig(config); + const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++ const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); + const tokenManager = ServerTokenManager.noop(); ++ const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); ++ ++ const identity = DefaultIdentityClient.create({ ++ discovery, ++ }); ++ const permissions = ServerPermissionClient.fromConfig(config, { ++ discovery, ++ tokenManager, ++ }); ++ ++ root.info(`Created UrlReader ${reader}`); + +@@ -46,3 +57,15 @@ function makeCreateEnv(config: Config) { + const cache = cacheManager.forPlugin(plugin); +- return { logger, database, cache, config, reader, discovery, tokenManager }; ++ const scheduler = taskScheduler.forPlugin(plugin); ++ return { ++ logger, ++ database, ++ cache, ++ config, ++ reader, ++ discovery, ++ tokenManager, ++ scheduler, ++ permissions, ++ identity, ++ }; + }; +@@ -72,2 +95,4 @@ async function main() { + apiRouter.use('/search', await search(searchEnv)); ++ ++ // Add backends ABOVE this line; this 404 handler is the catch-all fallback + apiRouter.use(notFoundHandler()); +@@ -87,3 +112,3 @@ module.hot?.accept(); + main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); ++ console.error('Backend failed to start up', error); + process.exit(1); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +index 07fb04f..7c37f68 100644 +--- a/packages/backend/src/plugins/app.ts ++++ b/packages/backend/src/plugins/app.ts +@@ -4,9 +4,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + return await createRouter({ +- logger, +- config, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, + appPackageName: 'app', +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +index 5216510..77eb6aa 100644 +--- a/packages/backend/src/plugins/auth.ts ++++ b/packages/backend/src/plugins/auth.ts +@@ -1,2 +1,6 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; ++import { ++ createRouter, ++ providers, ++ defaultAuthProviderFactories, ++} from '@backstage/plugin-auth-backend'; + import { Router } from 'express'; +@@ -4,9 +8,47 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, database, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, ++ providerFactories: { ++ ...defaultAuthProviderFactories, ++ ++ // This replaces the default GitHub auth provider with a customized one. ++ // The `signIn` option enables sign-in for this provider, using the ++ // identity resolution logic that's provided in the `resolver` callback. ++ // ++ // This particular resolver makes all users share a single "guest" identity. ++ // It should only be used for testing and trying out Backstage. ++ // ++ // If you want to use a production ready resolver you can switch to ++ // the one that is commented out below, it looks up a user entity in the ++ // catalog using the GitHub username of the authenticated user. ++ // That resolver requires you to have user entities populated in the catalog, ++ // for example using https://backstage.io/docs/integrations/github/org ++ // ++ // There are other resolvers to choose from, and you can also create ++ // your own, see the auth documentation for more details: ++ // ++ // https://backstage.io/docs/auth/identity-resolver ++ github: providers.github.create({ ++ signIn: { ++ resolver(_, ctx) { ++ const userRef = 'user:default/guest'; // Must be a full entity reference ++ return ctx.issueToken({ ++ claims: { ++ sub: userRef, // The user's own identity ++ ent: [userRef], // A list of identities that the user claims ownership through ++ }, ++ }); ++ }, ++ // resolver: providers.github.resolvers.usernameMatchingUserEntityName(), ++ }, ++ }), ++ }, ++ }); + } +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +index 506f6d9..54ec393 100644 +--- a/packages/backend/src/plugins/proxy.ts ++++ b/packages/backend/src/plugins/proxy.ts +@@ -4,8 +4,10 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ return await createRouter({ ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ }); + } +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +index 6be2e97..a12fee2 100644 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ b/packages/backend/src/plugins/scaffolder.ts +@@ -1,5 +1,3 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; + import { CatalogClient } from '@backstage/catalog-client'; + import { createRouter } from '@backstage/plugin-scaffolder-backend'; +-import Docker from 'dockerode'; + import { Router } from 'express'; +@@ -7,20 +5,17 @@ import type { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- database, +- reader, +- discovery, +-}: PluginEnvironment): Promise { +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- const catalogClient = new CatalogClient({ discoveryApi: discovery }); ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { ++ const catalogClient = new CatalogClient({ ++ discoveryApi: env.discovery, ++ }); + + return await createRouter({ +- containerRunner, +- logger, +- config, +- database, ++ logger: env.logger, ++ config: env.config, ++ database: env.database, ++ reader: env.reader, + catalogClient, +- reader, ++ identity: env.identity, ++ permissions: env.permissions, + }); +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +index f23b0c7..e9469dc 100644 +--- a/packages/backend/src/plugins/search.ts ++++ b/packages/backend/src/plugins/search.ts +@@ -7,14 +7,25 @@ import { + import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend'; ++import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend'; ++import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; ++import { Router } from 'express'; + +-export default async function createPlugin({ +- logger, +- discovery, +- config, +- tokenManager, +-}: PluginEnvironment) { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); ++ const searchEngine = new LunrSearchEngine({ ++ logger: env.logger, ++ }); ++ const indexBuilder = new IndexBuilder({ ++ logger: env.logger, ++ searchEngine, ++ }); ++ ++ const schedule = env.scheduler.createScheduledTaskRunner({ ++ frequency: { minutes: 10 }, ++ timeout: { minutes: 15 }, ++ // A 3 second delay gives the backend server a chance to initialize before ++ // any collators are executed, which may attempt requests against the API. ++ initialDelay: { seconds: 3 }, ++ }); + +@@ -23,6 +34,6 @@ export default async function createPlugin({ + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { +- discovery, +- tokenManager, ++ schedule, ++ factory: DefaultCatalogCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ tokenManager: env.tokenManager, + }), +@@ -32,7 +43,7 @@ export default async function createPlugin({ + indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultTechDocsCollator.fromConfig(config, { +- discovery, +- logger, +- tokenManager, ++ schedule, ++ factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { ++ discovery: env.discovery, ++ logger: env.logger, ++ tokenManager: env.tokenManager, + }), +@@ -43,6 +54,4 @@ export default async function createPlugin({ + const { scheduler } = await indexBuilder.build(); ++ scheduler.start(); + +- // A 3 second delay gives the backend server a chance to initialize before +- // any collators are executed, which may attempt requests against the API. +- setTimeout(() => scheduler.start(), 3000); + useHotCleanup(module, () => scheduler.stop()); +@@ -51,3 +60,6 @@ export default async function createPlugin({ + engine: indexBuilder.getSearchEngine(), +- logger, ++ types: indexBuilder.getDocumentTypes(), ++ permissions: env.permissions, ++ config: env.config, ++ logger: env.logger, + }); +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +index 054c64d..be8bb0c 100644 +--- a/packages/backend/src/plugins/techdocs.ts ++++ b/packages/backend/src/plugins/techdocs.ts +@@ -11,13 +11,9 @@ import { PluginEnvironment } from '../types'; + +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +- cache, +-}: PluginEnvironment): Promise { ++export default async function createPlugin( ++ env: PluginEnvironment, ++): Promise { + // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, ++ const preparers = await Preparers.fromConfig(env.config, { ++ logger: env.logger, ++ reader: env.reader, + }); +@@ -29,4 +25,4 @@ export default async function createPlugin({ + // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, ++ const generators = await Generators.fromConfig(env.config, { ++ logger: env.logger, + containerRunner, +@@ -37,5 +33,5 @@ export default async function createPlugin({ + // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, ++ const publisher = await Publisher.fromConfig(env.config, { ++ logger: env.logger, ++ discovery: env.discovery, + }); +@@ -49,6 +45,6 @@ export default async function createPlugin({ + publisher, +- logger, +- config, +- discovery, +- cache, ++ logger: env.logger, ++ config: env.config, ++ discovery: env.discovery, ++ cache: env.cache, + }); +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +index b1e2e0a..9cd2c74 100644 +--- a/packages/backend/src/types.ts ++++ b/packages/backend/src/types.ts +@@ -9,2 +9,5 @@ import { + } from '@backstage/backend-common'; ++import { PluginTaskScheduler } from '@backstage/backend-tasks'; ++import { PermissionEvaluator } from '@backstage/plugin-permission-common'; ++import { IdentityApi } from '@backstage/plugin-auth-node'; + +@@ -18,2 +21,5 @@ export type PluginEnvironment = { + tokenManager: TokenManager; ++ scheduler: PluginTaskScheduler; ++ permissions: PermissionEvaluator; ++ identity: IdentityApi; + }; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..37c7fb1 +--- /dev/null ++++ b/playwright.config.ts +@@ -0,0 +1,60 @@ ++/* ++ * Copyright 2023 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { defineConfig } from '@playwright/test'; ++import { generateProjects } from '@backstage/e2e-test-utils/playwright'; ++ ++/** ++ * See https://playwright.dev/docs/test-configuration. ++ */ ++export default defineConfig({ ++ timeout: 60_000, ++ ++ expect: { ++ timeout: 5_000, ++ }, ++ ++ // Run your local dev server before starting the tests ++ webServer: process.env.CI ++ ? [] ++ : [ ++ { ++ command: 'yarn start', ++ port: 3000, ++ reuseExistingServer: true, ++ timeout: 60_000, ++ }, ++ ], ++ ++ forbidOnly: !!process.env.CI, ++ ++ retries: process.env.CI ? 2 : 0, ++ ++ reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]], ++ ++ use: { ++ actionTimeout: 0, ++ baseURL: ++ process.env.PLAYWRIGHT_URL ?? ++ (process.env.CI ? 'http://localhost:7007' : 'http://localhost:3000'), ++ screenshot: 'only-on-failure', ++ trace: 'on-first-retry', ++ }, ++ ++ outputDir: 'node_modules/.cache/e2e-test-results', ++ ++ projects: generateProjects(), // Find all packages with e2e-test folders ++}); +diff --git a/plugins/README.md b/plugins/README.md +new file mode 100644 +index 0000000..d7865fd +--- /dev/null ++++ b/plugins/README.md +@@ -0,0 +1,9 @@ ++# The Plugins Folder ++ ++This is where your own plugins and their associated modules live, each in a ++separate folder of its own. ++ ++If you want to create a new plugin here, go to your project root directory, run ++the command `yarn new`, and follow the on-screen instructions. ++ ++You can also check out existing plugins on [the plugin marketplace](https://backstage.io/plugins)! +diff --git a/yarn.lock b/yarn.lock +new file mode 100644 +index 0000000..5ad7fe2 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1 @@ ++# intentionally left empty diff --git a/diffs/0.5.0..0.5.7-next.1.diff b/diffs/0.5.0..0.5.7-next.1.diff new file mode 100644 index 00000000..60d729b6 --- /dev/null +++ b/diffs/0.5.0..0.5.7-next.1.diff @@ -0,0 +1,439 @@ +diff --git a/.eslintignore b/.eslintignore +new file mode 100644 +index 0000000..e5b1994 +--- /dev/null ++++ b/.eslintignore +@@ -0,0 +1 @@ ++playwright.config.ts +diff --git a/.gitignore b/.gitignore +index d452ac2..fbf8139 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -51 +51,4 @@ site + *.session.sql ++ ++# E2E test reports ++e2e-test-report/ +diff --git a/app-config.yaml b/app-config.yaml +index 3d216ec..08d2abf 100644 +--- a/app-config.yaml ++++ b/app-config.yaml +@@ -32,4 +32,2 @@ backend: + connection: ':memory:' +- cache: +- store: memory + # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir +@@ -50,5 +48,6 @@ proxy: + ### A typical reason to do this is to handle HTTPS and CORS for internal services. +- # '/test': +- # target: 'https://example.com' +- # changeOrigin: true ++ # endpoints: ++ # '/test': ++ # target: 'https://example.com' ++ # changeOrigin: true + +diff --git a/backstage.json b/backstage.json +index a21904c..707af29 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "1.13.0" ++ "version": "1.20.0-next.1" + } +diff --git a/lerna.json b/lerna.json +index 322929d..529a62f 100644 +--- a/lerna.json ++++ b/lerna.json +@@ -3,4 +3,4 @@ + "npmClient": "yarn", +- "useWorkspaces": true, +- "version": "0.1.0" ++ "version": "0.1.0", ++ "$schema": "node_modules/lerna/schemas/lerna-schema.json" + } +diff --git a/package.json b/package.json +index 6a38c3c..2364da8 100644 +--- a/package.json ++++ b/package.json +@@ -5,3 +5,3 @@ + "engines": { +- "node": "16 || 18" ++ "node": "18 || 20" + }, +@@ -19,2 +19,4 @@ + "test:all": "backstage-cli repo test --coverage", ++ "test:e2e": "playwright test", ++ "fix": "backstage-cli repo fix", + "lint": "backstage-cli repo lint --since origin/master", +@@ -31,9 +33,11 @@ + "devDependencies": { +- "@backstage/cli": "^0.22.6", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/e2e-test-utils": "^0.1.0", ++ "@playwright/test": "^1.32.3", + "@spotify/prettier-config": "^12.0.0", +- "concurrently": "^6.0.0", +- "lerna": "^4.0.0", ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", + "node-gyp": "^9.0.0", + "prettier": "^2.3.2", +- "typescript": "~5.0.0" ++ "typescript": "~5.2.0" + }, +diff --git a/packages/app/cypress.json b/packages/app/cypress.json +deleted file mode 100644 +index 0cb845a..0000000 +--- a/packages/app/cypress.json ++++ /dev/null +@@ -1,6 +0,0 @@ +-{ +- "baseUrl": "http://localhost:3001", +- "fixturesFolder": false, +- "pluginsFile": false, +- "retries": 3 +-} +diff --git a/packages/app/cypress/.eslintrc.json b/packages/app/cypress/.eslintrc.json +deleted file mode 100644 +index a467608..0000000 +--- a/packages/app/cypress/.eslintrc.json ++++ /dev/null +@@ -1,12 +0,0 @@ +-{ +- "plugins": ["cypress"], +- "extends": ["plugin:cypress/recommended"], +- "rules": { +- "jest/expect-expect": [ +- "error", +- { +- "assertFunctionNames": ["expect", "cy.contains"] +- } +- ] +- } +-} +diff --git a/packages/app/cypress/integration/app.js b/packages/app/cypress/integration/app.js +deleted file mode 100644 +index 43fb2e3..0000000 +--- a/packages/app/cypress/integration/app.js ++++ /dev/null +@@ -1,6 +0,0 @@ +-describe('App', () => { +- it('should render the catalog', () => { +- cy.visit('/'); +- cy.contains('My Company Catalog'); +- }); +-}); +diff --git a/packages/app/e2e-tests/app.test.ts b/packages/app/e2e-tests/app.test.ts +new file mode 100644 +index 0000000..d45bc0d +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,23 @@ ++/* ++ * Copyright 2020 The Backstage Authors ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++import { test, expect } from '@playwright/test'; ++ ++test('App should render the welcome page', async ({ page }) => { ++ await page.goto('/'); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 84eef05..2100405 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -13,34 +13,30 @@ + "test": "backstage-cli package test", +- "lint": "backstage-cli package lint", +- "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", +- "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", +- "cy:dev": "cypress open", +- "cy:run": "cypress run --browser chrome" ++ "lint": "backstage-cli package lint" + }, + "dependencies": { +- "@backstage/app-defaults": "^1.3.0", +- "@backstage/catalog-model": "^1.3.0", +- "@backstage/cli": "^0.22.6", +- "@backstage/core-app-api": "^1.7.0", +- "@backstage/core-components": "^0.13.0", +- "@backstage/core-plugin-api": "^1.5.1", +- "@backstage/integration-react": "^1.1.12", +- "@backstage/plugin-api-docs": "^0.9.2", +- "@backstage/plugin-catalog": "^1.10.0", +- "@backstage/plugin-catalog-common": "^1.0.13", +- "@backstage/plugin-catalog-graph": "^0.2.29", +- "@backstage/plugin-catalog-import": "^0.9.7", +- "@backstage/plugin-catalog-react": "^1.5.0", +- "@backstage/plugin-github-actions": "^0.5.17", +- "@backstage/plugin-org": "^0.6.7", +- "@backstage/plugin-permission-react": "^0.4.12", +- "@backstage/plugin-scaffolder": "^1.13.0", +- "@backstage/plugin-search": "^1.2.0", +- "@backstage/plugin-search-react": "^1.5.2", +- "@backstage/plugin-tech-radar": "^0.6.3", +- "@backstage/plugin-techdocs": "^1.6.1", +- "@backstage/plugin-techdocs-module-addons-contrib": "^1.0.12", +- "@backstage/plugin-techdocs-react": "^1.1.5", +- "@backstage/plugin-user-settings": "^0.7.2", +- "@backstage/theme": "^0.2.19", ++ "@backstage/app-defaults": "^1.4.5-next.1", ++ "@backstage/catalog-model": "^1.4.3", ++ "@backstage/cli": "^0.24.0-next.1", ++ "@backstage/core-app-api": "^1.11.1-next.0", ++ "@backstage/core-components": "^0.13.8-next.1", ++ "@backstage/core-plugin-api": "^1.8.0-next.0", ++ "@backstage/integration-react": "^1.1.21-next.1", ++ "@backstage/plugin-api-docs": "^0.9.13-next.1", ++ "@backstage/plugin-catalog": "^1.15.0-next.1", ++ "@backstage/plugin-catalog-common": "^1.0.17", ++ "@backstage/plugin-catalog-graph": "^0.2.38-next.1", ++ "@backstage/plugin-catalog-import": "^0.10.2-next.1", ++ "@backstage/plugin-catalog-react": "^1.9.0-next.1", ++ "@backstage/plugin-github-actions": "^0.6.7-next.1", ++ "@backstage/plugin-org": "^0.6.16-next.1", ++ "@backstage/plugin-permission-react": "^0.4.17-next.0", ++ "@backstage/plugin-scaffolder": "^1.16.0-next.1", ++ "@backstage/plugin-search": "^1.4.2-next.1", ++ "@backstage/plugin-search-react": "^1.7.2-next.1", ++ "@backstage/plugin-tech-radar": "^0.6.10-next.1", ++ "@backstage/plugin-techdocs": "^1.8.1-next.1", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.2-next.1", ++ "@backstage/plugin-techdocs-react": "^1.1.13-next.1", ++ "@backstage/plugin-user-settings": "^0.7.12-next.1", ++ "@backstage/theme": "^0.4.4-next.0", + "@material-ui/core": "^4.12.2", +@@ -55,3 +51,4 @@ + "devDependencies": { +- "@backstage/test-utils": "^1.3.0", ++ "@backstage/test-utils": "^1.4.5-next.0", ++ "@playwright/test": "^1.32.3", + "@testing-library/jest-dom": "^5.10.1", +@@ -60,8 +57,4 @@ + "@testing-library/dom": "^8.0.0", +- "@types/node": "^16.11.26", + "@types/react-dom": "*", +- "cross-env": "^7.0.0", +- "cypress": "^9.7.0", +- "eslint-plugin-cypress": "^2.10.3", +- "start-server-and-test": "^1.10.11" ++ "cross-env": "^7.0.0" + }, +diff --git a/packages/app/public/index.html b/packages/app/public/index.html +index c6083b3..fc758ee 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -10,3 +10,2 @@ + /> +- +