From c506e55b3ea570c899bb5f73cc58bda08e690ace Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 20 Aug 2024 09:56:19 +0000 Subject: [PATCH] Add release 0.5.18 --- diffs/0.3.44..0.5.18.diff | 2092 +++++++++++++++++++++++++++++ diffs/0.3.45..0.5.18.diff | 2094 +++++++++++++++++++++++++++++ diffs/0.4.0..0.5.18.diff | 2086 +++++++++++++++++++++++++++++ diffs/0.4.1..0.5.18.diff | 2086 +++++++++++++++++++++++++++++ diffs/0.4.10..0.5.18.diff | 2066 +++++++++++++++++++++++++++++ diffs/0.4.11..0.5.18.diff | 2066 +++++++++++++++++++++++++++++ diffs/0.4.12..0.5.18.diff | 2076 +++++++++++++++++++++++++++++ diffs/0.4.13..0.5.18.diff | 2045 +++++++++++++++++++++++++++++ diffs/0.4.14..0.5.18.diff | 2037 +++++++++++++++++++++++++++++ diffs/0.4.15..0.5.18.diff | 1940 +++++++++++++++++++++++++++ diffs/0.4.16..0.5.18.diff | 1936 +++++++++++++++++++++++++++ diffs/0.4.18..0.5.18.diff | 1944 +++++++++++++++++++++++++++ diffs/0.4.19..0.5.18.diff | 1943 +++++++++++++++++++++++++++ diffs/0.4.20..0.5.18.diff | 1928 +++++++++++++++++++++++++++ diffs/0.4.21..0.5.18.diff | 1930 +++++++++++++++++++++++++++ diffs/0.4.22..0.5.18.diff | 1916 +++++++++++++++++++++++++++ diffs/0.4.23..0.5.18.diff | 1869 ++++++++++++++++++++++++++ diffs/0.4.24..0.5.18.diff | 1861 ++++++++++++++++++++++++++ diffs/0.4.25..0.5.18.diff | 1863 ++++++++++++++++++++++++++ diffs/0.4.26..0.5.18.diff | 1837 ++++++++++++++++++++++++++ diffs/0.4.27..0.5.18.diff | 1615 +++++++++++++++++++++++ diffs/0.4.28..0.5.18.diff | 1596 +++++++++++++++++++++++ diffs/0.4.29..0.5.18.diff | 1597 +++++++++++++++++++++++ diffs/0.4.3..0.5.18.diff | 2081 +++++++++++++++++++++++++++++ diffs/0.4.30..0.5.18.diff | 1587 ++++++++++++++++++++++ diffs/0.4.31..0.5.18.diff | 1548 ++++++++++++++++++++++ diffs/0.4.32..0.5.18.diff | 1389 ++++++++++++++++++++ diffs/0.4.33..0.5.18.diff | 1389 ++++++++++++++++++++ diffs/0.4.34..0.5.18.diff | 1290 ++++++++++++++++++ diffs/0.4.35..0.5.18.diff | 1242 ++++++++++++++++++ diffs/0.4.36..0.5.18.diff | 1238 ++++++++++++++++++ diffs/0.4.37-next.0..0.5.18.diff | 1238 ++++++++++++++++++ diffs/0.4.37-next.1..0.5.18.diff | 1185 +++++++++++++++++ diffs/0.4.37-next.2..0.5.18.diff | 1176 +++++++++++++++++ diffs/0.4.37..0.5.18.diff | 1176 +++++++++++++++++ diffs/0.4.38-next.0..0.5.18.diff | 1176 +++++++++++++++++ diffs/0.4.38-next.1..0.5.18.diff | 1167 +++++++++++++++++ diffs/0.4.38-next.2..0.5.18.diff | 1156 ++++++++++++++++ diffs/0.4.38..0.5.18.diff | 1149 ++++++++++++++++ diffs/0.4.39-next.0..0.5.18.diff | 1149 ++++++++++++++++ diffs/0.4.39-next.1..0.5.18.diff | 1150 ++++++++++++++++ diffs/0.4.39-next.2..0.5.18.diff | 1150 ++++++++++++++++ diffs/0.4.39-next.3..0.5.18.diff | 1150 ++++++++++++++++ diffs/0.4.4..0.5.18.diff | 2082 +++++++++++++++++++++++++++++ diffs/0.4.5..0.5.18.diff | 2088 +++++++++++++++++++++++++++++ diffs/0.4.6..0.5.18.diff | 2101 ++++++++++++++++++++++++++++++ diffs/0.4.7..0.5.18.diff | 2097 +++++++++++++++++++++++++++++ diffs/0.4.8..0.5.18.diff | 2092 +++++++++++++++++++++++++++++ diffs/0.4.9..0.5.18.diff | 2089 +++++++++++++++++++++++++++++ diffs/0.5.0..0.5.18.diff | 1150 ++++++++++++++++ diffs/0.5.1-next.0..0.5.18.diff | 1150 ++++++++++++++++ diffs/0.5.1-next.1..0.5.18.diff | 1153 ++++++++++++++++ diffs/0.5.1-next.2..0.5.18.diff | 1144 ++++++++++++++++ diffs/0.5.1..0.5.18.diff | 1141 ++++++++++++++++ diffs/0.5.10..0.5.18.diff | 848 ++++++++++++ diffs/0.5.11-next.0..0.5.18.diff | 848 ++++++++++++ diffs/0.5.11-next.1..0.5.18.diff | 848 ++++++++++++ diffs/0.5.11-next.2..0.5.18.diff | 839 ++++++++++++ diffs/0.5.11-next.3..0.5.18.diff | 834 ++++++++++++ diffs/0.5.11..0.5.18.diff | 832 ++++++++++++ diffs/0.5.12-next.0..0.5.18.diff | 832 ++++++++++++ diffs/0.5.12-next.1..0.5.18.diff | 832 ++++++++++++ diffs/0.5.12-next.2..0.5.18.diff | 823 ++++++++++++ diffs/0.5.12..0.5.18.diff | 382 ++++++ diffs/0.5.13..0.5.18.diff | 382 ++++++ diffs/0.5.14-next.0..0.5.18.diff | 382 ++++++ diffs/0.5.14-next.1..0.5.18.diff | 382 ++++++ diffs/0.5.14..0.5.18.diff | 377 ++++++ diffs/0.5.15-next.0..0.5.18.diff | 295 +++++ diffs/0.5.15-next.1..0.5.18.diff | 295 +++++ diffs/0.5.15-next.2..0.5.18.diff | 295 +++++ diffs/0.5.15..0.5.18.diff | 295 +++++ diffs/0.5.16-next.0..0.5.18.diff | 295 +++++ diffs/0.5.16-next.1..0.5.18.diff | 295 +++++ diffs/0.5.16-next.2..0.5.18.diff | 296 +++++ diffs/0.5.16-next.3..0.5.18.diff | 282 ++++ diffs/0.5.16..0.5.18.diff | 275 ++++ diffs/0.5.17-next.0..0.5.18.diff | 275 ++++ diffs/0.5.17-next.1..0.5.18.diff | 258 ++++ diffs/0.5.17-next.2..0.5.18.diff | 254 ++++ diffs/0.5.17..0.5.18.diff | 251 ++++ diffs/0.5.18-next.0..0.5.18.diff | 194 +++ diffs/0.5.18-next.1..0.5.18.diff | 155 +++ diffs/0.5.18-next.2..0.5.18.diff | 155 +++ diffs/0.5.18-next.3..0.5.18.diff | 155 +++ diffs/0.5.18-next.4..0.5.18.diff | 136 ++ diffs/0.5.2-next.0..0.5.18.diff | 1141 ++++++++++++++++ diffs/0.5.2-next.1..0.5.18.diff | 1133 ++++++++++++++++ diffs/0.5.2-next.2..0.5.18.diff | 1133 ++++++++++++++++ diffs/0.5.2-next.3..0.5.18.diff | 1133 ++++++++++++++++ diffs/0.5.2..0.5.18.diff | 1128 ++++++++++++++++ diffs/0.5.3-next.0..0.5.18.diff | 1128 ++++++++++++++++ diffs/0.5.3-next.1..0.5.18.diff | 1114 ++++++++++++++++ diffs/0.5.3-next.2..0.5.18.diff | 1105 ++++++++++++++++ diffs/0.5.3..0.5.18.diff | 1105 ++++++++++++++++ diffs/0.5.4-next.0..0.5.18.diff | 1105 ++++++++++++++++ diffs/0.5.4-next.1..0.5.18.diff | 1105 ++++++++++++++++ diffs/0.5.4-next.2..0.5.18.diff | 1105 ++++++++++++++++ diffs/0.5.4..0.5.18.diff | 1094 ++++++++++++++++ diffs/0.5.5-next.0..0.5.18.diff | 1094 ++++++++++++++++ diffs/0.5.5-next.1..0.5.18.diff | 1094 ++++++++++++++++ diffs/0.5.5-next.2..0.5.18.diff | 1089 ++++++++++++++++ diffs/0.5.5-next.3..0.5.18.diff | 1088 ++++++++++++++++ diffs/0.5.5..0.5.18.diff | 1088 ++++++++++++++++ diffs/0.5.6-next.0..0.5.18.diff | 929 +++++++++++++ diffs/0.5.6-next.1..0.5.18.diff | 922 +++++++++++++ diffs/0.5.6-next.2..0.5.18.diff | 922 +++++++++++++ diffs/0.5.6..0.5.18.diff | 909 +++++++++++++ diffs/0.5.7-next.0..0.5.18.diff | 909 +++++++++++++ diffs/0.5.7-next.1..0.5.18.diff | 909 +++++++++++++ diffs/0.5.7-next.2..0.5.18.diff | 910 +++++++++++++ diffs/0.5.7..0.5.18.diff | 910 +++++++++++++ diffs/0.5.8-next.0..0.5.18.diff | 910 +++++++++++++ diffs/0.5.8-next.1..0.5.18.diff | 910 +++++++++++++ diffs/0.5.8-next.2..0.5.18.diff | 910 +++++++++++++ diffs/0.5.8-next.3..0.5.18.diff | 900 +++++++++++++ diffs/0.5.8-next.4..0.5.18.diff | 900 +++++++++++++ diffs/0.5.8..0.5.18.diff | 900 +++++++++++++ diffs/0.5.9-next.0..0.5.18.diff | 900 +++++++++++++ diffs/0.5.9-next.1..0.5.18.diff | 900 +++++++++++++ diffs/0.5.9-next.2..0.5.18.diff | 900 +++++++++++++ diffs/0.5.9..0.5.18.diff | 900 +++++++++++++ releases.json | 3 + 123 files changed, 138728 insertions(+) create mode 100644 diffs/0.3.44..0.5.18.diff create mode 100644 diffs/0.3.45..0.5.18.diff create mode 100644 diffs/0.4.0..0.5.18.diff create mode 100644 diffs/0.4.1..0.5.18.diff create mode 100644 diffs/0.4.10..0.5.18.diff create mode 100644 diffs/0.4.11..0.5.18.diff create mode 100644 diffs/0.4.12..0.5.18.diff create mode 100644 diffs/0.4.13..0.5.18.diff create mode 100644 diffs/0.4.14..0.5.18.diff create mode 100644 diffs/0.4.15..0.5.18.diff create mode 100644 diffs/0.4.16..0.5.18.diff create mode 100644 diffs/0.4.18..0.5.18.diff create mode 100644 diffs/0.4.19..0.5.18.diff create mode 100644 diffs/0.4.20..0.5.18.diff create mode 100644 diffs/0.4.21..0.5.18.diff create mode 100644 diffs/0.4.22..0.5.18.diff create mode 100644 diffs/0.4.23..0.5.18.diff create mode 100644 diffs/0.4.24..0.5.18.diff create mode 100644 diffs/0.4.25..0.5.18.diff create mode 100644 diffs/0.4.26..0.5.18.diff create mode 100644 diffs/0.4.27..0.5.18.diff create mode 100644 diffs/0.4.28..0.5.18.diff create mode 100644 diffs/0.4.29..0.5.18.diff create mode 100644 diffs/0.4.3..0.5.18.diff create mode 100644 diffs/0.4.30..0.5.18.diff create mode 100644 diffs/0.4.31..0.5.18.diff create mode 100644 diffs/0.4.32..0.5.18.diff create mode 100644 diffs/0.4.33..0.5.18.diff create mode 100644 diffs/0.4.34..0.5.18.diff create mode 100644 diffs/0.4.35..0.5.18.diff create mode 100644 diffs/0.4.36..0.5.18.diff create mode 100644 diffs/0.4.37-next.0..0.5.18.diff create mode 100644 diffs/0.4.37-next.1..0.5.18.diff create mode 100644 diffs/0.4.37-next.2..0.5.18.diff create mode 100644 diffs/0.4.37..0.5.18.diff create mode 100644 diffs/0.4.38-next.0..0.5.18.diff create mode 100644 diffs/0.4.38-next.1..0.5.18.diff create mode 100644 diffs/0.4.38-next.2..0.5.18.diff create mode 100644 diffs/0.4.38..0.5.18.diff create mode 100644 diffs/0.4.39-next.0..0.5.18.diff create mode 100644 diffs/0.4.39-next.1..0.5.18.diff create mode 100644 diffs/0.4.39-next.2..0.5.18.diff create mode 100644 diffs/0.4.39-next.3..0.5.18.diff create mode 100644 diffs/0.4.4..0.5.18.diff create mode 100644 diffs/0.4.5..0.5.18.diff create mode 100644 diffs/0.4.6..0.5.18.diff create mode 100644 diffs/0.4.7..0.5.18.diff create mode 100644 diffs/0.4.8..0.5.18.diff create mode 100644 diffs/0.4.9..0.5.18.diff create mode 100644 diffs/0.5.0..0.5.18.diff create mode 100644 diffs/0.5.1-next.0..0.5.18.diff create mode 100644 diffs/0.5.1-next.1..0.5.18.diff create mode 100644 diffs/0.5.1-next.2..0.5.18.diff create mode 100644 diffs/0.5.1..0.5.18.diff create mode 100644 diffs/0.5.10..0.5.18.diff create mode 100644 diffs/0.5.11-next.0..0.5.18.diff create mode 100644 diffs/0.5.11-next.1..0.5.18.diff create mode 100644 diffs/0.5.11-next.2..0.5.18.diff create mode 100644 diffs/0.5.11-next.3..0.5.18.diff create mode 100644 diffs/0.5.11..0.5.18.diff create mode 100644 diffs/0.5.12-next.0..0.5.18.diff create mode 100644 diffs/0.5.12-next.1..0.5.18.diff create mode 100644 diffs/0.5.12-next.2..0.5.18.diff create mode 100644 diffs/0.5.12..0.5.18.diff create mode 100644 diffs/0.5.13..0.5.18.diff create mode 100644 diffs/0.5.14-next.0..0.5.18.diff create mode 100644 diffs/0.5.14-next.1..0.5.18.diff create mode 100644 diffs/0.5.14..0.5.18.diff create mode 100644 diffs/0.5.15-next.0..0.5.18.diff create mode 100644 diffs/0.5.15-next.1..0.5.18.diff create mode 100644 diffs/0.5.15-next.2..0.5.18.diff create mode 100644 diffs/0.5.15..0.5.18.diff create mode 100644 diffs/0.5.16-next.0..0.5.18.diff create mode 100644 diffs/0.5.16-next.1..0.5.18.diff create mode 100644 diffs/0.5.16-next.2..0.5.18.diff create mode 100644 diffs/0.5.16-next.3..0.5.18.diff create mode 100644 diffs/0.5.16..0.5.18.diff create mode 100644 diffs/0.5.17-next.0..0.5.18.diff create mode 100644 diffs/0.5.17-next.1..0.5.18.diff create mode 100644 diffs/0.5.17-next.2..0.5.18.diff create mode 100644 diffs/0.5.17..0.5.18.diff create mode 100644 diffs/0.5.18-next.0..0.5.18.diff create mode 100644 diffs/0.5.18-next.1..0.5.18.diff create mode 100644 diffs/0.5.18-next.2..0.5.18.diff create mode 100644 diffs/0.5.18-next.3..0.5.18.diff create mode 100644 diffs/0.5.18-next.4..0.5.18.diff create mode 100644 diffs/0.5.2-next.0..0.5.18.diff create mode 100644 diffs/0.5.2-next.1..0.5.18.diff create mode 100644 diffs/0.5.2-next.2..0.5.18.diff create mode 100644 diffs/0.5.2-next.3..0.5.18.diff create mode 100644 diffs/0.5.2..0.5.18.diff create mode 100644 diffs/0.5.3-next.0..0.5.18.diff create mode 100644 diffs/0.5.3-next.1..0.5.18.diff create mode 100644 diffs/0.5.3-next.2..0.5.18.diff create mode 100644 diffs/0.5.3..0.5.18.diff create mode 100644 diffs/0.5.4-next.0..0.5.18.diff create mode 100644 diffs/0.5.4-next.1..0.5.18.diff create mode 100644 diffs/0.5.4-next.2..0.5.18.diff create mode 100644 diffs/0.5.4..0.5.18.diff create mode 100644 diffs/0.5.5-next.0..0.5.18.diff create mode 100644 diffs/0.5.5-next.1..0.5.18.diff create mode 100644 diffs/0.5.5-next.2..0.5.18.diff create mode 100644 diffs/0.5.5-next.3..0.5.18.diff create mode 100644 diffs/0.5.5..0.5.18.diff create mode 100644 diffs/0.5.6-next.0..0.5.18.diff create mode 100644 diffs/0.5.6-next.1..0.5.18.diff create mode 100644 diffs/0.5.6-next.2..0.5.18.diff create mode 100644 diffs/0.5.6..0.5.18.diff create mode 100644 diffs/0.5.7-next.0..0.5.18.diff create mode 100644 diffs/0.5.7-next.1..0.5.18.diff create mode 100644 diffs/0.5.7-next.2..0.5.18.diff create mode 100644 diffs/0.5.7..0.5.18.diff create mode 100644 diffs/0.5.8-next.0..0.5.18.diff create mode 100644 diffs/0.5.8-next.1..0.5.18.diff create mode 100644 diffs/0.5.8-next.2..0.5.18.diff create mode 100644 diffs/0.5.8-next.3..0.5.18.diff create mode 100644 diffs/0.5.8-next.4..0.5.18.diff create mode 100644 diffs/0.5.8..0.5.18.diff create mode 100644 diffs/0.5.9-next.0..0.5.18.diff create mode 100644 diffs/0.5.9-next.1..0.5.18.diff create mode 100644 diffs/0.5.9-next.2..0.5.18.diff create mode 100644 diffs/0.5.9..0.5.18.diff diff --git a/diffs/0.3.44..0.5.18.diff b/diffs/0.3.44..0.5.18.diff new file mode 100644 index 00000000..d511ae7a --- /dev/null +++ b/diffs/0.3.44..0.5.18.diff @@ -0,0 +1,2092 @@ +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..5d426f5 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -1,8 +1,55 @@ + 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/apis/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 ++ ++auth: ++ providers: ++ guest: {} ++ ++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: ++ # Local example data, replace this with your production config, these are intended for demo use only. ++ # File locations are relative to the backend process, typically in a deployed context, such as in a Docker container, this will be the root ++ - type: file ++ target: ./examples/entities.yaml ++ ++ # Local example template ++ - type: file ++ target: ./examples/template/template.yaml ++ rules: ++ - allow: [Template] ++ ++ # Local example organizational data ++ - type: file ++ target: ./examples/org.yaml ++ rules: ++ - allow: [User, Group] +diff --git a/app-config.yaml b/app-config.yaml +index 93b0c3f..ca52ec5 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/integrations/github/locations#configuration + 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 + +@@ -53,49 +66,48 @@ auth: + # see https://backstage.io/docs/auth/ to learn about auth providers +- providers: {} ++ providers: ++ # See https://backstage.io/docs/auth/guest/provider ++ guest: {} + + 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 ++ # 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 ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml ++ rules: ++ - allow: [Template] + +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml ++ rules: ++ - allow: [User, Group] + +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml + +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.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] + +- # 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 +- 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 +- rules: +- - allow: [Template] ++kubernetes: ++ # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options ++ ++# see https://backstage.io/docs/permissions/getting-started for more on the permission framework ++permission: ++ # setting this to `false` will disable permissions ++ enabled: true +diff --git a/backstage.json b/backstage.json +new file mode 100644 +index 0000000..230d39d +--- /dev/null ++++ b/backstage.json +@@ -0,0 +1,3 @@ ++{ ++ "version": "1.30.0" ++} +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..80a963e 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.27.0", ++ "@backstage/e2e-test-utils": "^0.1.1", ++ "@playwright/test": "^1.32.3", ++ "@spotify/prettier-config": "^12.0.0", ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^10.0.0", ++ "prettier": "^2.3.2", ++ "typescript": "~5.4.0" ++ }, ++ "resolutions": { ++ "@types/react": "^18", ++ "@types/react-dom": "^18" + }, +@@ -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..839ff88 +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,27 @@ ++/* ++ * 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('/'); ++ ++ const enterButton = page.getByRole('button', { name: 'Enter' }); ++ await expect(enterButton).toBeVisible(); ++ await enterButton.click(); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 6d348b5..346ffc1 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,22 +5,37 @@ + "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.5.10", ++ "@backstage/catalog-model": "^1.6.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/core-app-api": "^1.14.2", ++ "@backstage/core-components": "^0.14.10", ++ "@backstage/core-plugin-api": "^1.9.3", ++ "@backstage/integration-react": "^1.1.30", ++ "@backstage/plugin-api-docs": "^0.11.8", ++ "@backstage/plugin-catalog": "^1.22.0", ++ "@backstage/plugin-catalog-common": "^1.0.26", ++ "@backstage/plugin-catalog-graph": "^0.4.8", ++ "@backstage/plugin-catalog-import": "^0.12.2", ++ "@backstage/plugin-catalog-react": "^1.12.3", ++ "@backstage/plugin-kubernetes": "^0.11.13", ++ "@backstage/plugin-org": "^0.6.28", ++ "@backstage/plugin-permission-react": "^0.4.25", ++ "@backstage/plugin-scaffolder": "^1.24.0", ++ "@backstage/plugin-search": "^1.4.15", ++ "@backstage/plugin-search-react": "^1.7.14", ++ "@backstage/plugin-techdocs": "^1.10.8", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.13", ++ "@backstage/plugin-techdocs-react": "^1.2.7", ++ "@backstage/plugin-user-settings": "^0.8.11", ++ "@backstage/theme": "^0.5.6", + "@material-ui/core": "^4.12.2", +@@ -28,30 +43,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": "^18.0.2", ++ "react-dom": "^18.0.2", ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@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", ++ "@backstage/test-utils": "^1.5.10", ++ "@playwright/test": "^1.32.3", ++ "@testing-library/jest-dom": "^6.0.0", ++ "@testing-library/react": "^14.0.0", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^9.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..18da7c4 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -8,5 +8,4 @@ + name="description" +- content="Backstage is an open platform for building developer portals" ++ content="Backstage is an open source framework for building developer portals" + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index 82bc479..ec8ba1d 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { renderWithEffects } from '@backstage/test-utils'; ++import { render, waitFor } from '@testing-library/react'; + import App from './App'; +@@ -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', + }, +@@ -22,4 +22,7 @@ describe('App', () => { + +- const rendered = await renderWithEffects(); +- expect(rendered.baseElement).toBeInTheDocument(); ++ const rendered = render(); ++ ++ await waitFor(() => { ++ expect(rendered.baseElement).toBeInTheDocument(); ++ }); + }); +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 4cd8368..e24c468 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,6 +13,5 @@ import { + import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; ++import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +-import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -21,2 +20,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'; +@@ -27,4 +28,12 @@ import { Root } from './components/Root'; + +-import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; +-import { createApp, FlatRoutes } from '@backstage/core-app-api'; ++import { ++ AlertDisplay, ++ OAuthRequestDialog, ++ SignInPage, ++} from '@backstage/core-components'; ++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 +45,6 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); + bind(apiDocsPlugin.externalRoutes, { +- createComponent: scaffolderPlugin.routes.root, ++ registerApi: catalogImportPlugin.routes.importPage, + }); +@@ -42,3 +52,10 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); ++ bind(orgPlugin.externalRoutes, { ++ catalogIndex: catalogPlugin.routes.catalogIndex, ++ }); ++ }, ++ components: { ++ SignInPage: props => , + }, +@@ -46,8 +63,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -59,5 +73,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -69,6 +85,9 @@ const routes = ( + } ++ path="/catalog-import" ++ element={ ++ ++ ++ ++ } + /> +- } /> + }> +@@ -77,2 +96,3 @@ const routes = ( + } /> ++ } /> + +@@ -80,4 +100,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -87,5 +107,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..853d175 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,22 +1,5 @@ +-/* +- * 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'; + import ExtensionIcon from '@material-ui/icons/Extension'; +-import MapIcon from '@material-ui/icons/MyLocation'; + import LibraryBooks from '@material-ui/icons/LibraryBooks'; +@@ -25,15 +8,23 @@ 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'; ++import { MyGroupsSidebarItem } from '@backstage/plugin-org'; ++import GroupIcon from '@material-ui/icons/People'; + +@@ -56,3 +47,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -60,8 +51,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -76,17 +62,32 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ } to="/search"> ++ ++ + +- {/* Global nav, not org-specific */} +- +- +- +- +- {/* End global nav */} +- +- +- +- ++ }> ++ {/* Global nav, not org-specific */} ++ ++ ++ ++ ++ ++ {/* End global nav */} ++ ++ ++ {/* Items in this group will be scrollable if they run out of space */} ++ ++ + + +- ++ } ++ 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..6164c76 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,7 +27,5 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +-import { +- isGithubActionsAvailable, +- EntityGithubActionsContent, +-} from '@backstage/plugin-github-actions'; + import { +@@ -56,2 +38,32 @@ 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'; ++ ++import { ++ EntityKubernetesContent, ++ isKubernetesAvailable, ++} from '@backstage/plugin-kubernetes'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); + +@@ -61,5 +73,9 @@ const cicdContent = ( + +- +- +- ++ {/* ++ Here you can add support for different CI/CD services, for example ++ using @backstage-community/plugin-github-actions as follows: ++ ++ ++ ++ */} + +@@ -94,2 +110,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -110,2 +134,6 @@ const overviewContent = ( + ++ ++ ++ ++ + +@@ -129,2 +157,10 @@ const serviceEntityPage = ( + ++ ++ ++ ++ + +@@ -152,3 +188,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -167,2 +203,10 @@ const websiteEntityPage = ( + ++ ++ ++ ++ + +@@ -179,3 +223,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -198,3 +242,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -225,2 +269,5 @@ const apiPage = ( + ++ ++ ++ + +@@ -276,5 +323,8 @@ const groupPage = ( + +- ++ + + ++ ++ ++ + +@@ -292,3 +342,9 @@ const systemPage = ( + +- ++ ++ ++ ++ ++ ++ ++ + +@@ -304,3 +360,19 @@ const systemPage = ( + +- ++ + +@@ -317,2 +389,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/app/src/index.tsx b/packages/app/src/index.tsx +index b16aaf7..d875c77 100644 +--- a/packages/app/src/index.tsx ++++ b/packages/app/src/index.tsx +@@ -2,5 +2,5 @@ import '@backstage/cli/asset-types'; + import React from 'react'; +-import ReactDOM from 'react-dom'; ++import ReactDOM from 'react-dom/client'; + import App from './App'; + +-ReactDOM.render(, document.getElementById('root')); ++ReactDOM.createRoot(document.getElementById('root')!).render(); +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..bef67b5 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,15 @@ 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 ++ ++# This will include the examples, if you don't need these simply remove this line ++COPY --chown=node:node examples ./examples + + # 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..6a14abc 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,32 +6,43 @@ + "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", +- "express": "^4.17.1", +- "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "@backstage/backend-common": "^0.24.0", ++ "@backstage/backend-defaults": "^0.4.2", ++ "@backstage/backend-tasks": "^0.6.0", ++ "@backstage/config": "^1.2.0", ++ "@backstage/plugin-app-backend": "^0.3.72", ++ "@backstage/plugin-auth-backend": "^0.22.10", ++ "@backstage/plugin-auth-backend-module-github-provider": "^0.1.20", ++ "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.9", ++ "@backstage/plugin-auth-node": "^0.5.0", ++ "@backstage/plugin-catalog-backend": "^1.25.0", ++ "@backstage/plugin-catalog-backend-module-logs": "^0.0.2", ++ "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.21", ++ "@backstage/plugin-kubernetes-backend": "^0.18.4", ++ "@backstage/plugin-permission-backend": "^0.5.47", ++ "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.1.20", ++ "@backstage/plugin-permission-common": "^0.8.1", ++ "@backstage/plugin-permission-node": "^0.8.1", ++ "@backstage/plugin-proxy-backend": "^0.5.4", ++ "@backstage/plugin-scaffolder-backend": "^1.24.0", ++ "@backstage/plugin-search-backend": "^1.5.15", ++ "@backstage/plugin-search-backend-module-catalog": "^0.2.0", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.33", ++ "@backstage/plugin-search-backend-module-techdocs": "^0.2.0", ++ "@backstage/plugin-search-backend-node": "^1.3.0", ++ "@backstage/plugin-techdocs-backend": "^1.10.10", ++ "app": "link:../app", ++ "better-sqlite3": "^9.0.0", ++ "node-gyp": "^10.0.0", ++ "pg": "^8.11.3", + "winston": "^3.2.1" +@@ -39,6 +50,6 @@ + "devDependencies": { +- "@backstage/cli": "^0.7.15", +- "@types/dockerode": "^3.2.1", ++ "@backstage/cli": "^0.27.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.test.ts b/packages/backend/src/index.test.ts +deleted file mode 100644 +index 7814b8c..0000000 +--- a/packages/backend/src/index.test.ts ++++ /dev/null +@@ -1,8 +0,0 @@ +-import { PluginEnvironment } from './types'; +- +-describe('test', () => { +- it('unbreaks the test runner', () => { +- const unbreaker = {} as PluginEnvironment; +- expect(unbreaker).toBeTruthy(); +- }); +-}); +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index f2b14b2..f57d4c5 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -8,81 +8,47 @@ + +-import Router from 'express-promise-router'; +-import { +- createServiceBuilder, +- loadBackendConfig, +- getRootLogger, +- useHotMemoize, +- notFoundHandler, +- CacheManager, +- DatabaseManager, +- SingleHostDiscovery, +- UrlReaders, +-} from '@backstage/backend-common'; +-import { Config } from '@backstage/config'; +-import app from './plugins/app'; +-import auth from './plugins/auth'; +-import catalog from './plugins/catalog'; +-import scaffolder from './plugins/scaffolder'; +-import proxy from './plugins/proxy'; +-import techdocs from './plugins/techdocs'; +-import search from './plugins/search'; +-import { PluginEnvironment } from './types'; ++import { createBackend } from '@backstage/backend-defaults'; + +-function makeCreateEnv(config: Config) { +- const root = getRootLogger(); +- const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++const backend = createBackend(); + +- root.info(`Created UrlReader ${reader}`); ++backend.add(import('@backstage/plugin-app-backend/alpha')); ++backend.add(import('@backstage/plugin-proxy-backend/alpha')); ++backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); ++backend.add(import('@backstage/plugin-techdocs-backend/alpha')); + +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++// auth plugin ++backend.add(import('@backstage/plugin-auth-backend')); ++// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin ++backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); ++// See https://backstage.io/docs/auth/guest/provider + +- return (plugin: string): PluginEnvironment => { +- const logger = root.child({ type: 'plugin', plugin }); +- const database = databaseManager.forPlugin(plugin); +- const cache = cacheManager.forPlugin(plugin); +- return { logger, database, cache, config, reader, discovery }; +- }; +-} ++// catalog plugin ++backend.add(import('@backstage/plugin-catalog-backend/alpha')); ++backend.add( ++ import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ++); + +-async function main() { +- const config = await loadBackendConfig({ +- argv: process.argv, +- logger: getRootLogger(), +- }); +- const createEnv = makeCreateEnv(config); ++// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors ++backend.add(import('@backstage/plugin-catalog-backend-module-logs')); + +- const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); +- const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); +- const authEnv = useHotMemoize(module, () => createEnv('auth')); +- const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); +- const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); +- const searchEnv = useHotMemoize(module, () => createEnv('search')); +- const appEnv = useHotMemoize(module, () => createEnv('app')); ++// permission plugin ++backend.add(import('@backstage/plugin-permission-backend/alpha')); ++// See https://backstage.io/docs/permissions/getting-started for how to create your own permission policy ++backend.add( ++ import('@backstage/plugin-permission-backend-module-allow-all-policy'), ++); + +- const apiRouter = Router(); +- apiRouter.use('/catalog', await catalog(catalogEnv)); +- apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); +- apiRouter.use('/auth', await auth(authEnv)); +- apiRouter.use('/techdocs', await techdocs(techdocsEnv)); +- apiRouter.use('/proxy', await proxy(proxyEnv)); +- apiRouter.use('/search', await search(searchEnv)); +- apiRouter.use(notFoundHandler()); ++// search plugin ++backend.add(import('@backstage/plugin-search-backend/alpha')); + +- const service = createServiceBuilder(module) +- .loadConfig(config) +- .addRouter('/api', apiRouter) +- .addRouter('', await app(appEnv)); ++// search engine ++// See https://backstage.io/docs/features/search/search-engines ++backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); + +- await service.start().catch(err => { +- console.log(err); +- process.exit(1); +- }); +-} ++// search collators ++backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); ++backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); + +-module.hot?.accept(); +-main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); +- process.exit(1); +-}); ++// kubernetes ++backend.add(import('@backstage/plugin-kubernetes-backend/alpha')); ++ ++backend.start(); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +deleted file mode 100644 +index 07fb04f..0000000 +--- a/packages/backend/src/plugins/app.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { createRouter } from '@backstage/plugin-app-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +-}: PluginEnvironment): Promise { +- return await createRouter({ +- logger, +- config, +- appPackageName: 'app', +- }); +-} +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +deleted file mode 100644 +index 5216510..0000000 +--- a/packages/backend/src/plugins/auth.ts ++++ /dev/null +@@ -1,12 +0,0 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, database, discovery }); +-} +diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts +deleted file mode 100644 +index d1ded51..0000000 +--- a/packages/backend/src/plugins/catalog.ts ++++ /dev/null +@@ -1,12 +0,0 @@ +-import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const builder = await CatalogBuilder.create(env); +- const { processingEngine, router } = await builder.build(); +- await processingEngine.start(); +- return router; +-} +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +deleted file mode 100644 +index 506f6d9..0000000 +--- a/packages/backend/src/plugins/proxy.ts ++++ /dev/null +@@ -1,11 +0,0 @@ +-import { createRouter } from '@backstage/plugin-proxy-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); +-} +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +deleted file mode 100644 +index 6be2e97..0000000 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ /dev/null +@@ -1,27 +0,0 @@ +-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'; +-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 }); +- +- return await createRouter({ +- containerRunner, +- logger, +- config, +- database, +- catalogClient, +- reader, +- }); +-} +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +deleted file mode 100644 +index 7fc317d..0000000 +--- a/packages/backend/src/plugins/search.ts ++++ /dev/null +@@ -1,39 +0,0 @@ +-import { useHotCleanup } from '@backstage/backend-common'; +-import { createRouter } from '@backstage/plugin-search-backend'; +-import { +- IndexBuilder, +- LunrSearchEngine, +-} from '@backstage/plugin-search-backend-node'; +-import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +- +-export default async function createPlugin({ +- logger, +- discovery, +- config, +-}: PluginEnvironment) { +- // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); +- +- // Collators are responsible for gathering documents known to plugins. This +- // particular collator gathers entities from the software catalog. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { discovery }), +- }); +- +- // The scheduler controls when documents are gathered from collators and sent +- // to the search engine for indexing. +- const { scheduler } = await indexBuilder.build(); +- +- // 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()); +- +- return await createRouter({ +- engine: indexBuilder.getSearchEngine(), +- logger, +- }); +-} +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +deleted file mode 100644 +index 906d86d..0000000 +--- a/packages/backend/src/plugins/techdocs.ts ++++ /dev/null +@@ -1,53 +0,0 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; +-import { +- createRouter, +- Generators, +- Preparers, +- Publisher, +-} from '@backstage/plugin-techdocs-backend'; +-import Docker from 'dockerode'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +-}: PluginEnvironment): Promise { +- // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, +- }); +- +- // Docker client (conditionally) used by the generators, based on techdocs.generators config. +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- +- // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, +- containerRunner, +- }); +- +- // Publisher is used for +- // 1. Publishing generated files to storage +- // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, +- }); +- +- // checks if the publisher is working and logs the result +- await publisher.getReadiness(); +- +- return await createRouter({ +- preparers, +- generators, +- publisher, +- logger, +- config, +- discovery, +- }); +-} +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +deleted file mode 100644 +index 6c78a2a..0000000 +--- a/packages/backend/src/types.ts ++++ /dev/null +@@ -1,17 +0,0 @@ +-import { Logger } from 'winston'; +-import { Config } from '@backstage/config'; +-import { +- PluginCacheManager, +- PluginDatabaseManager, +- PluginEndpointDiscovery, +- UrlReader, +-} from '@backstage/backend-common'; +- +-export type PluginEnvironment = { +- logger: Logger; +- database: PluginDatabaseManager; +- cache: PluginCacheManager; +- config: Config; +- reader: UrlReader; +- discovery: PluginEndpointDiscovery; +-}; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..733be13 +--- /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 dev', ++ 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..c37fc61 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1,3 @@ ++# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. ++# yarn lockfile v1 ++ diff --git a/diffs/0.3.45..0.5.18.diff b/diffs/0.3.45..0.5.18.diff new file mode 100644 index 00000000..b2935832 --- /dev/null +++ b/diffs/0.3.45..0.5.18.diff @@ -0,0 +1,2094 @@ +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..5d426f5 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -1,8 +1,55 @@ + 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/apis/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 ++ ++auth: ++ providers: ++ guest: {} ++ ++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: ++ # Local example data, replace this with your production config, these are intended for demo use only. ++ # File locations are relative to the backend process, typically in a deployed context, such as in a Docker container, this will be the root ++ - type: file ++ target: ./examples/entities.yaml ++ ++ # Local example template ++ - type: file ++ target: ./examples/template/template.yaml ++ rules: ++ - allow: [Template] ++ ++ # Local example organizational data ++ - type: file ++ target: ./examples/org.yaml ++ rules: ++ - allow: [User, Group] +diff --git a/app-config.yaml b/app-config.yaml +index 93b0c3f..ca52ec5 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/integrations/github/locations#configuration + 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 + +@@ -53,49 +66,48 @@ auth: + # see https://backstage.io/docs/auth/ to learn about auth providers +- providers: {} ++ providers: ++ # See https://backstage.io/docs/auth/guest/provider ++ guest: {} + + 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 ++ # 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 ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml ++ rules: ++ - allow: [Template] + +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml ++ rules: ++ - allow: [User, Group] + +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml + +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.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] + +- # 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 +- 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 +- rules: +- - allow: [Template] ++kubernetes: ++ # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options ++ ++# see https://backstage.io/docs/permissions/getting-started for more on the permission framework ++permission: ++ # setting this to `false` will disable permissions ++ enabled: true +diff --git a/backstage.json b/backstage.json +new file mode 100644 +index 0000000..230d39d +--- /dev/null ++++ b/backstage.json +@@ -0,0 +1,3 @@ ++{ ++ "version": "1.30.0" ++} +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..80a963e 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.27.0", ++ "@backstage/e2e-test-utils": "^0.1.1", ++ "@playwright/test": "^1.32.3", ++ "@spotify/prettier-config": "^12.0.0", ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^10.0.0", ++ "prettier": "^2.3.2", ++ "typescript": "~5.4.0" ++ }, ++ "resolutions": { ++ "@types/react": "^18", ++ "@types/react-dom": "^18" + }, +@@ -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..839ff88 +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,27 @@ ++/* ++ * 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('/'); ++ ++ const enterButton = page.getByRole('button', { name: 'Enter' }); ++ await expect(enterButton).toBeVisible(); ++ await enterButton.click(); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index a860127..346ffc1 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,22 +5,37 @@ + "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.5.10", ++ "@backstage/catalog-model": "^1.6.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/core-app-api": "^1.14.2", ++ "@backstage/core-components": "^0.14.10", ++ "@backstage/core-plugin-api": "^1.9.3", ++ "@backstage/integration-react": "^1.1.30", ++ "@backstage/plugin-api-docs": "^0.11.8", ++ "@backstage/plugin-catalog": "^1.22.0", ++ "@backstage/plugin-catalog-common": "^1.0.26", ++ "@backstage/plugin-catalog-graph": "^0.4.8", ++ "@backstage/plugin-catalog-import": "^0.12.2", ++ "@backstage/plugin-catalog-react": "^1.12.3", ++ "@backstage/plugin-kubernetes": "^0.11.13", ++ "@backstage/plugin-org": "^0.6.28", ++ "@backstage/plugin-permission-react": "^0.4.25", ++ "@backstage/plugin-scaffolder": "^1.24.0", ++ "@backstage/plugin-search": "^1.4.15", ++ "@backstage/plugin-search-react": "^1.7.14", ++ "@backstage/plugin-techdocs": "^1.10.8", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.13", ++ "@backstage/plugin-techdocs-react": "^1.2.7", ++ "@backstage/plugin-user-settings": "^0.8.11", ++ "@backstage/theme": "^0.5.6", + "@material-ui/core": "^4.12.2", +@@ -28,30 +43,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": "^18.0.2", ++ "react-dom": "^18.0.2", ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@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", ++ "@backstage/test-utils": "^1.5.10", ++ "@playwright/test": "^1.32.3", ++ "@testing-library/jest-dom": "^6.0.0", ++ "@testing-library/react": "^14.0.0", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^9.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..18da7c4 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -8,5 +8,4 @@ + name="description" +- content="Backstage is an open platform for building developer portals" ++ content="Backstage is an open source framework for building developer portals" + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index 82bc479..ec8ba1d 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { renderWithEffects } from '@backstage/test-utils'; ++import { render, waitFor } from '@testing-library/react'; + import App from './App'; +@@ -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', + }, +@@ -22,4 +22,7 @@ describe('App', () => { + +- const rendered = await renderWithEffects(); +- expect(rendered.baseElement).toBeInTheDocument(); ++ const rendered = render(); ++ ++ await waitFor(() => { ++ expect(rendered.baseElement).toBeInTheDocument(); ++ }); + }); +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 4cd8368..e24c468 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,6 +13,5 @@ import { + import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; ++import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +-import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -21,2 +20,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'; +@@ -27,4 +28,12 @@ import { Root } from './components/Root'; + +-import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; +-import { createApp, FlatRoutes } from '@backstage/core-app-api'; ++import { ++ AlertDisplay, ++ OAuthRequestDialog, ++ SignInPage, ++} from '@backstage/core-components'; ++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 +45,6 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); + bind(apiDocsPlugin.externalRoutes, { +- createComponent: scaffolderPlugin.routes.root, ++ registerApi: catalogImportPlugin.routes.importPage, + }); +@@ -42,3 +52,10 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); ++ bind(orgPlugin.externalRoutes, { ++ catalogIndex: catalogPlugin.routes.catalogIndex, ++ }); ++ }, ++ components: { ++ SignInPage: props => , + }, +@@ -46,8 +63,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -59,5 +73,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -69,6 +85,9 @@ const routes = ( + } ++ path="/catalog-import" ++ element={ ++ ++ ++ ++ } + /> +- } /> + }> +@@ -77,2 +96,3 @@ const routes = ( + } /> ++ } /> + +@@ -80,4 +100,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -87,5 +107,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..853d175 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,22 +1,5 @@ +-/* +- * 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'; + import ExtensionIcon from '@material-ui/icons/Extension'; +-import MapIcon from '@material-ui/icons/MyLocation'; + import LibraryBooks from '@material-ui/icons/LibraryBooks'; +@@ -25,15 +8,23 @@ 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'; ++import { MyGroupsSidebarItem } from '@backstage/plugin-org'; ++import GroupIcon from '@material-ui/icons/People'; + +@@ -56,3 +47,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -60,8 +51,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -76,17 +62,32 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ } to="/search"> ++ ++ + +- {/* Global nav, not org-specific */} +- +- +- +- +- {/* End global nav */} +- +- +- +- ++ }> ++ {/* Global nav, not org-specific */} ++ ++ ++ ++ ++ ++ {/* End global nav */} ++ ++ ++ {/* Items in this group will be scrollable if they run out of space */} ++ ++ + + +- ++ } ++ 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..6164c76 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,7 +27,5 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +-import { +- isGithubActionsAvailable, +- EntityGithubActionsContent, +-} from '@backstage/plugin-github-actions'; + import { +@@ -56,2 +38,32 @@ 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'; ++ ++import { ++ EntityKubernetesContent, ++ isKubernetesAvailable, ++} from '@backstage/plugin-kubernetes'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); + +@@ -61,5 +73,9 @@ const cicdContent = ( + +- +- +- ++ {/* ++ Here you can add support for different CI/CD services, for example ++ using @backstage-community/plugin-github-actions as follows: ++ ++ ++ ++ */} + +@@ -94,2 +110,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -110,2 +134,6 @@ const overviewContent = ( + ++ ++ ++ ++ + +@@ -129,2 +157,10 @@ const serviceEntityPage = ( + ++ ++ ++ ++ + +@@ -152,3 +188,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -167,2 +203,10 @@ const websiteEntityPage = ( + ++ ++ ++ ++ + +@@ -179,3 +223,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -198,3 +242,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -225,2 +269,5 @@ const apiPage = ( + ++ ++ ++ + +@@ -276,5 +323,8 @@ const groupPage = ( + +- ++ + + ++ ++ ++ + +@@ -292,3 +342,9 @@ const systemPage = ( + +- ++ ++ ++ ++ ++ ++ ++ + +@@ -304,3 +360,19 @@ const systemPage = ( + +- ++ + +@@ -317,2 +389,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/app/src/index.tsx b/packages/app/src/index.tsx +index b16aaf7..d875c77 100644 +--- a/packages/app/src/index.tsx ++++ b/packages/app/src/index.tsx +@@ -2,5 +2,5 @@ import '@backstage/cli/asset-types'; + import React from 'react'; +-import ReactDOM from 'react-dom'; ++import ReactDOM from 'react-dom/client'; + import App from './App'; + +-ReactDOM.render(, document.getElementById('root')); ++ReactDOM.createRoot(document.getElementById('root')!).render(); +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..bef67b5 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,15 @@ 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 ++ ++# This will include the examples, if you don't need these simply remove this line ++COPY --chown=node:node examples ./examples + + # 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..6a14abc 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,32 +6,43 @@ + "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", +- "express": "^4.17.1", +- "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "@backstage/backend-common": "^0.24.0", ++ "@backstage/backend-defaults": "^0.4.2", ++ "@backstage/backend-tasks": "^0.6.0", ++ "@backstage/config": "^1.2.0", ++ "@backstage/plugin-app-backend": "^0.3.72", ++ "@backstage/plugin-auth-backend": "^0.22.10", ++ "@backstage/plugin-auth-backend-module-github-provider": "^0.1.20", ++ "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.9", ++ "@backstage/plugin-auth-node": "^0.5.0", ++ "@backstage/plugin-catalog-backend": "^1.25.0", ++ "@backstage/plugin-catalog-backend-module-logs": "^0.0.2", ++ "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.21", ++ "@backstage/plugin-kubernetes-backend": "^0.18.4", ++ "@backstage/plugin-permission-backend": "^0.5.47", ++ "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.1.20", ++ "@backstage/plugin-permission-common": "^0.8.1", ++ "@backstage/plugin-permission-node": "^0.8.1", ++ "@backstage/plugin-proxy-backend": "^0.5.4", ++ "@backstage/plugin-scaffolder-backend": "^1.24.0", ++ "@backstage/plugin-search-backend": "^1.5.15", ++ "@backstage/plugin-search-backend-module-catalog": "^0.2.0", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.33", ++ "@backstage/plugin-search-backend-module-techdocs": "^0.2.0", ++ "@backstage/plugin-search-backend-node": "^1.3.0", ++ "@backstage/plugin-techdocs-backend": "^1.10.10", ++ "app": "link:../app", ++ "better-sqlite3": "^9.0.0", ++ "node-gyp": "^10.0.0", ++ "pg": "^8.11.3", + "winston": "^3.2.1" +@@ -39,6 +50,6 @@ + "devDependencies": { +- "@backstage/cli": "^0.7.16", +- "@types/dockerode": "^3.2.1", ++ "@backstage/cli": "^0.27.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.test.ts b/packages/backend/src/index.test.ts +deleted file mode 100644 +index 7814b8c..0000000 +--- a/packages/backend/src/index.test.ts ++++ /dev/null +@@ -1,8 +0,0 @@ +-import { PluginEnvironment } from './types'; +- +-describe('test', () => { +- it('unbreaks the test runner', () => { +- const unbreaker = {} as PluginEnvironment; +- expect(unbreaker).toBeTruthy(); +- }); +-}); +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index f2b14b2..f57d4c5 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -8,81 +8,47 @@ + +-import Router from 'express-promise-router'; +-import { +- createServiceBuilder, +- loadBackendConfig, +- getRootLogger, +- useHotMemoize, +- notFoundHandler, +- CacheManager, +- DatabaseManager, +- SingleHostDiscovery, +- UrlReaders, +-} from '@backstage/backend-common'; +-import { Config } from '@backstage/config'; +-import app from './plugins/app'; +-import auth from './plugins/auth'; +-import catalog from './plugins/catalog'; +-import scaffolder from './plugins/scaffolder'; +-import proxy from './plugins/proxy'; +-import techdocs from './plugins/techdocs'; +-import search from './plugins/search'; +-import { PluginEnvironment } from './types'; ++import { createBackend } from '@backstage/backend-defaults'; + +-function makeCreateEnv(config: Config) { +- const root = getRootLogger(); +- const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++const backend = createBackend(); + +- root.info(`Created UrlReader ${reader}`); ++backend.add(import('@backstage/plugin-app-backend/alpha')); ++backend.add(import('@backstage/plugin-proxy-backend/alpha')); ++backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); ++backend.add(import('@backstage/plugin-techdocs-backend/alpha')); + +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++// auth plugin ++backend.add(import('@backstage/plugin-auth-backend')); ++// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin ++backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); ++// See https://backstage.io/docs/auth/guest/provider + +- return (plugin: string): PluginEnvironment => { +- const logger = root.child({ type: 'plugin', plugin }); +- const database = databaseManager.forPlugin(plugin); +- const cache = cacheManager.forPlugin(plugin); +- return { logger, database, cache, config, reader, discovery }; +- }; +-} ++// catalog plugin ++backend.add(import('@backstage/plugin-catalog-backend/alpha')); ++backend.add( ++ import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ++); + +-async function main() { +- const config = await loadBackendConfig({ +- argv: process.argv, +- logger: getRootLogger(), +- }); +- const createEnv = makeCreateEnv(config); ++// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors ++backend.add(import('@backstage/plugin-catalog-backend-module-logs')); + +- const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); +- const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); +- const authEnv = useHotMemoize(module, () => createEnv('auth')); +- const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); +- const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); +- const searchEnv = useHotMemoize(module, () => createEnv('search')); +- const appEnv = useHotMemoize(module, () => createEnv('app')); ++// permission plugin ++backend.add(import('@backstage/plugin-permission-backend/alpha')); ++// See https://backstage.io/docs/permissions/getting-started for how to create your own permission policy ++backend.add( ++ import('@backstage/plugin-permission-backend-module-allow-all-policy'), ++); + +- const apiRouter = Router(); +- apiRouter.use('/catalog', await catalog(catalogEnv)); +- apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); +- apiRouter.use('/auth', await auth(authEnv)); +- apiRouter.use('/techdocs', await techdocs(techdocsEnv)); +- apiRouter.use('/proxy', await proxy(proxyEnv)); +- apiRouter.use('/search', await search(searchEnv)); +- apiRouter.use(notFoundHandler()); ++// search plugin ++backend.add(import('@backstage/plugin-search-backend/alpha')); + +- const service = createServiceBuilder(module) +- .loadConfig(config) +- .addRouter('/api', apiRouter) +- .addRouter('', await app(appEnv)); ++// search engine ++// See https://backstage.io/docs/features/search/search-engines ++backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); + +- await service.start().catch(err => { +- console.log(err); +- process.exit(1); +- }); +-} ++// search collators ++backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); ++backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); + +-module.hot?.accept(); +-main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); +- process.exit(1); +-}); ++// kubernetes ++backend.add(import('@backstage/plugin-kubernetes-backend/alpha')); ++ ++backend.start(); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +deleted file mode 100644 +index 07fb04f..0000000 +--- a/packages/backend/src/plugins/app.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { createRouter } from '@backstage/plugin-app-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +-}: PluginEnvironment): Promise { +- return await createRouter({ +- logger, +- config, +- appPackageName: 'app', +- }); +-} +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +deleted file mode 100644 +index 5216510..0000000 +--- a/packages/backend/src/plugins/auth.ts ++++ /dev/null +@@ -1,12 +0,0 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, database, discovery }); +-} +diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts +deleted file mode 100644 +index 876cb6b..0000000 +--- a/packages/backend/src/plugins/catalog.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +-import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const builder = await CatalogBuilder.create(env); +- builder.addProcessor(new ScaffolderEntitiesProcessor()); +- const { processingEngine, router } = await builder.build(); +- await processingEngine.start(); +- return router; +-} +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +deleted file mode 100644 +index 506f6d9..0000000 +--- a/packages/backend/src/plugins/proxy.ts ++++ /dev/null +@@ -1,11 +0,0 @@ +-import { createRouter } from '@backstage/plugin-proxy-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); +-} +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +deleted file mode 100644 +index 6be2e97..0000000 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ /dev/null +@@ -1,27 +0,0 @@ +-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'; +-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 }); +- +- return await createRouter({ +- containerRunner, +- logger, +- config, +- database, +- catalogClient, +- reader, +- }); +-} +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +deleted file mode 100644 +index 7fc317d..0000000 +--- a/packages/backend/src/plugins/search.ts ++++ /dev/null +@@ -1,39 +0,0 @@ +-import { useHotCleanup } from '@backstage/backend-common'; +-import { createRouter } from '@backstage/plugin-search-backend'; +-import { +- IndexBuilder, +- LunrSearchEngine, +-} from '@backstage/plugin-search-backend-node'; +-import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +- +-export default async function createPlugin({ +- logger, +- discovery, +- config, +-}: PluginEnvironment) { +- // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); +- +- // Collators are responsible for gathering documents known to plugins. This +- // particular collator gathers entities from the software catalog. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { discovery }), +- }); +- +- // The scheduler controls when documents are gathered from collators and sent +- // to the search engine for indexing. +- const { scheduler } = await indexBuilder.build(); +- +- // 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()); +- +- return await createRouter({ +- engine: indexBuilder.getSearchEngine(), +- logger, +- }); +-} +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +deleted file mode 100644 +index 906d86d..0000000 +--- a/packages/backend/src/plugins/techdocs.ts ++++ /dev/null +@@ -1,53 +0,0 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; +-import { +- createRouter, +- Generators, +- Preparers, +- Publisher, +-} from '@backstage/plugin-techdocs-backend'; +-import Docker from 'dockerode'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +-}: PluginEnvironment): Promise { +- // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, +- }); +- +- // Docker client (conditionally) used by the generators, based on techdocs.generators config. +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- +- // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, +- containerRunner, +- }); +- +- // Publisher is used for +- // 1. Publishing generated files to storage +- // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, +- }); +- +- // checks if the publisher is working and logs the result +- await publisher.getReadiness(); +- +- return await createRouter({ +- preparers, +- generators, +- publisher, +- logger, +- config, +- discovery, +- }); +-} +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +deleted file mode 100644 +index 6c78a2a..0000000 +--- a/packages/backend/src/types.ts ++++ /dev/null +@@ -1,17 +0,0 @@ +-import { Logger } from 'winston'; +-import { Config } from '@backstage/config'; +-import { +- PluginCacheManager, +- PluginDatabaseManager, +- PluginEndpointDiscovery, +- UrlReader, +-} from '@backstage/backend-common'; +- +-export type PluginEnvironment = { +- logger: Logger; +- database: PluginDatabaseManager; +- cache: PluginCacheManager; +- config: Config; +- reader: UrlReader; +- discovery: PluginEndpointDiscovery; +-}; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..733be13 +--- /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 dev', ++ 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..c37fc61 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1,3 @@ ++# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. ++# yarn lockfile v1 ++ diff --git a/diffs/0.4.0..0.5.18.diff b/diffs/0.4.0..0.5.18.diff new file mode 100644 index 00000000..5cc39883 --- /dev/null +++ b/diffs/0.4.0..0.5.18.diff @@ -0,0 +1,2086 @@ +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..5d426f5 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -1,8 +1,55 @@ + 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/apis/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 ++ ++auth: ++ providers: ++ guest: {} ++ ++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: ++ # Local example data, replace this with your production config, these are intended for demo use only. ++ # File locations are relative to the backend process, typically in a deployed context, such as in a Docker container, this will be the root ++ - type: file ++ target: ./examples/entities.yaml ++ ++ # Local example template ++ - type: file ++ target: ./examples/template/template.yaml ++ rules: ++ - allow: [Template] ++ ++ # Local example organizational data ++ - type: file ++ target: ./examples/org.yaml ++ rules: ++ - allow: [User, Group] +diff --git a/app-config.yaml b/app-config.yaml +index 93b0c3f..ca52ec5 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/integrations/github/locations#configuration + 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 + +@@ -53,49 +66,48 @@ auth: + # see https://backstage.io/docs/auth/ to learn about auth providers +- providers: {} ++ providers: ++ # See https://backstage.io/docs/auth/guest/provider ++ guest: {} + + 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 ++ # 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 ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml ++ rules: ++ - allow: [Template] + +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml ++ rules: ++ - allow: [User, Group] + +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml + +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.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] + +- # 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 +- 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 +- rules: +- - allow: [Template] ++kubernetes: ++ # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options ++ ++# see https://backstage.io/docs/permissions/getting-started for more on the permission framework ++permission: ++ # setting this to `false` will disable permissions ++ enabled: true +diff --git a/backstage.json b/backstage.json +new file mode 100644 +index 0000000..230d39d +--- /dev/null ++++ b/backstage.json +@@ -0,0 +1,3 @@ ++{ ++ "version": "1.30.0" ++} +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..80a963e 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.27.0", ++ "@backstage/e2e-test-utils": "^0.1.1", ++ "@playwright/test": "^1.32.3", ++ "@spotify/prettier-config": "^12.0.0", ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^10.0.0", ++ "prettier": "^2.3.2", ++ "typescript": "~5.4.0" ++ }, ++ "resolutions": { ++ "@types/react": "^18", ++ "@types/react-dom": "^18" + }, +@@ -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..839ff88 +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,27 @@ ++/* ++ * 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('/'); ++ ++ const enterButton = page.getByRole('button', { name: 'Enter' }); ++ await expect(enterButton).toBeVisible(); ++ await enterButton.click(); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 8bdb8f7..346ffc1 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,22 +5,37 @@ + "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.5.10", ++ "@backstage/catalog-model": "^1.6.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/core-app-api": "^1.14.2", ++ "@backstage/core-components": "^0.14.10", ++ "@backstage/core-plugin-api": "^1.9.3", ++ "@backstage/integration-react": "^1.1.30", ++ "@backstage/plugin-api-docs": "^0.11.8", ++ "@backstage/plugin-catalog": "^1.22.0", ++ "@backstage/plugin-catalog-common": "^1.0.26", ++ "@backstage/plugin-catalog-graph": "^0.4.8", ++ "@backstage/plugin-catalog-import": "^0.12.2", ++ "@backstage/plugin-catalog-react": "^1.12.3", ++ "@backstage/plugin-kubernetes": "^0.11.13", ++ "@backstage/plugin-org": "^0.6.28", ++ "@backstage/plugin-permission-react": "^0.4.25", ++ "@backstage/plugin-scaffolder": "^1.24.0", ++ "@backstage/plugin-search": "^1.4.15", ++ "@backstage/plugin-search-react": "^1.7.14", ++ "@backstage/plugin-techdocs": "^1.10.8", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.13", ++ "@backstage/plugin-techdocs-react": "^1.2.7", ++ "@backstage/plugin-user-settings": "^0.8.11", ++ "@backstage/theme": "^0.5.6", + "@material-ui/core": "^4.12.2", +@@ -28,30 +43,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": "^18.0.2", ++ "react-dom": "^18.0.2", ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@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", ++ "@backstage/test-utils": "^1.5.10", ++ "@playwright/test": "^1.32.3", ++ "@testing-library/jest-dom": "^6.0.0", ++ "@testing-library/react": "^14.0.0", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^9.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..18da7c4 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -8,5 +8,4 @@ + name="description" +- content="Backstage is an open platform for building developer portals" ++ content="Backstage is an open source framework for building developer portals" + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index 82bc479..ec8ba1d 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { renderWithEffects } from '@backstage/test-utils'; ++import { render, waitFor } from '@testing-library/react'; + import App from './App'; +@@ -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', + }, +@@ -22,4 +22,7 @@ describe('App', () => { + +- const rendered = await renderWithEffects(); +- expect(rendered.baseElement).toBeInTheDocument(); ++ const rendered = render(); ++ ++ await waitFor(() => { ++ expect(rendered.baseElement).toBeInTheDocument(); ++ }); + }); +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 4cd8368..e24c468 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,6 +13,5 @@ import { + import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; ++import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +-import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -21,2 +20,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'; +@@ -27,4 +28,12 @@ import { Root } from './components/Root'; + +-import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; +-import { createApp, FlatRoutes } from '@backstage/core-app-api'; ++import { ++ AlertDisplay, ++ OAuthRequestDialog, ++ SignInPage, ++} from '@backstage/core-components'; ++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 +45,6 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); + bind(apiDocsPlugin.externalRoutes, { +- createComponent: scaffolderPlugin.routes.root, ++ registerApi: catalogImportPlugin.routes.importPage, + }); +@@ -42,3 +52,10 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); ++ bind(orgPlugin.externalRoutes, { ++ catalogIndex: catalogPlugin.routes.catalogIndex, ++ }); ++ }, ++ components: { ++ SignInPage: props => , + }, +@@ -46,8 +63,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -59,5 +73,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -69,6 +85,9 @@ const routes = ( + } ++ path="/catalog-import" ++ element={ ++ ++ ++ ++ } + /> +- } /> + }> +@@ -77,2 +96,3 @@ const routes = ( + } /> ++ } /> + +@@ -80,4 +100,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -87,5 +107,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..853d175 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,22 +1,5 @@ +-/* +- * 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'; + import ExtensionIcon from '@material-ui/icons/Extension'; +-import MapIcon from '@material-ui/icons/MyLocation'; + import LibraryBooks from '@material-ui/icons/LibraryBooks'; +@@ -25,15 +8,23 @@ 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'; ++import { MyGroupsSidebarItem } from '@backstage/plugin-org'; ++import GroupIcon from '@material-ui/icons/People'; + +@@ -56,3 +47,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -60,8 +51,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -76,17 +62,32 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ } to="/search"> ++ ++ + +- {/* Global nav, not org-specific */} +- +- +- +- +- {/* End global nav */} +- +- +- +- ++ }> ++ {/* Global nav, not org-specific */} ++ ++ ++ ++ ++ ++ {/* End global nav */} ++ ++ ++ {/* Items in this group will be scrollable if they run out of space */} ++ ++ + + +- ++ } ++ 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..6164c76 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,7 +27,5 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +-import { +- isGithubActionsAvailable, +- EntityGithubActionsContent, +-} from '@backstage/plugin-github-actions'; + import { +@@ -56,2 +38,32 @@ 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'; ++ ++import { ++ EntityKubernetesContent, ++ isKubernetesAvailable, ++} from '@backstage/plugin-kubernetes'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); + +@@ -61,5 +73,9 @@ const cicdContent = ( + +- +- +- ++ {/* ++ Here you can add support for different CI/CD services, for example ++ using @backstage-community/plugin-github-actions as follows: ++ ++ ++ ++ */} + +@@ -94,2 +110,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -110,2 +134,6 @@ const overviewContent = ( + ++ ++ ++ ++ + +@@ -129,2 +157,10 @@ const serviceEntityPage = ( + ++ ++ ++ ++ + +@@ -152,3 +188,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -167,2 +203,10 @@ const websiteEntityPage = ( + ++ ++ ++ ++ + +@@ -179,3 +223,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -198,3 +242,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -225,2 +269,5 @@ const apiPage = ( + ++ ++ ++ + +@@ -276,5 +323,8 @@ const groupPage = ( + +- ++ + + ++ ++ ++ + +@@ -292,3 +342,9 @@ const systemPage = ( + +- ++ ++ ++ ++ ++ ++ ++ + +@@ -304,3 +360,19 @@ const systemPage = ( + +- ++ + +@@ -317,2 +389,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/app/src/index.tsx b/packages/app/src/index.tsx +index b16aaf7..d875c77 100644 +--- a/packages/app/src/index.tsx ++++ b/packages/app/src/index.tsx +@@ -2,5 +2,5 @@ import '@backstage/cli/asset-types'; + import React from 'react'; +-import ReactDOM from 'react-dom'; ++import ReactDOM from 'react-dom/client'; + import App from './App'; + +-ReactDOM.render(, document.getElementById('root')); ++ReactDOM.createRoot(document.getElementById('root')!).render(); +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..bef67b5 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,15 @@ 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 ++ ++# This will include the examples, if you don't need these simply remove this line ++COPY --chown=node:node examples ./examples + + # 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..6a14abc 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,32 +6,43 @@ + "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", +- "express": "^4.17.1", +- "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "@backstage/backend-common": "^0.24.0", ++ "@backstage/backend-defaults": "^0.4.2", ++ "@backstage/backend-tasks": "^0.6.0", ++ "@backstage/config": "^1.2.0", ++ "@backstage/plugin-app-backend": "^0.3.72", ++ "@backstage/plugin-auth-backend": "^0.22.10", ++ "@backstage/plugin-auth-backend-module-github-provider": "^0.1.20", ++ "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.9", ++ "@backstage/plugin-auth-node": "^0.5.0", ++ "@backstage/plugin-catalog-backend": "^1.25.0", ++ "@backstage/plugin-catalog-backend-module-logs": "^0.0.2", ++ "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.21", ++ "@backstage/plugin-kubernetes-backend": "^0.18.4", ++ "@backstage/plugin-permission-backend": "^0.5.47", ++ "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.1.20", ++ "@backstage/plugin-permission-common": "^0.8.1", ++ "@backstage/plugin-permission-node": "^0.8.1", ++ "@backstage/plugin-proxy-backend": "^0.5.4", ++ "@backstage/plugin-scaffolder-backend": "^1.24.0", ++ "@backstage/plugin-search-backend": "^1.5.15", ++ "@backstage/plugin-search-backend-module-catalog": "^0.2.0", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.33", ++ "@backstage/plugin-search-backend-module-techdocs": "^0.2.0", ++ "@backstage/plugin-search-backend-node": "^1.3.0", ++ "@backstage/plugin-techdocs-backend": "^1.10.10", ++ "app": "link:../app", ++ "better-sqlite3": "^9.0.0", ++ "node-gyp": "^10.0.0", ++ "pg": "^8.11.3", + "winston": "^3.2.1" +@@ -39,6 +50,6 @@ + "devDependencies": { +- "@backstage/cli": "^0.8.0", +- "@types/dockerode": "^3.2.1", ++ "@backstage/cli": "^0.27.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.test.ts b/packages/backend/src/index.test.ts +deleted file mode 100644 +index 7814b8c..0000000 +--- a/packages/backend/src/index.test.ts ++++ /dev/null +@@ -1,8 +0,0 @@ +-import { PluginEnvironment } from './types'; +- +-describe('test', () => { +- it('unbreaks the test runner', () => { +- const unbreaker = {} as PluginEnvironment; +- expect(unbreaker).toBeTruthy(); +- }); +-}); +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index f2b14b2..f57d4c5 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -8,81 +8,47 @@ + +-import Router from 'express-promise-router'; +-import { +- createServiceBuilder, +- loadBackendConfig, +- getRootLogger, +- useHotMemoize, +- notFoundHandler, +- CacheManager, +- DatabaseManager, +- SingleHostDiscovery, +- UrlReaders, +-} from '@backstage/backend-common'; +-import { Config } from '@backstage/config'; +-import app from './plugins/app'; +-import auth from './plugins/auth'; +-import catalog from './plugins/catalog'; +-import scaffolder from './plugins/scaffolder'; +-import proxy from './plugins/proxy'; +-import techdocs from './plugins/techdocs'; +-import search from './plugins/search'; +-import { PluginEnvironment } from './types'; ++import { createBackend } from '@backstage/backend-defaults'; + +-function makeCreateEnv(config: Config) { +- const root = getRootLogger(); +- const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++const backend = createBackend(); + +- root.info(`Created UrlReader ${reader}`); ++backend.add(import('@backstage/plugin-app-backend/alpha')); ++backend.add(import('@backstage/plugin-proxy-backend/alpha')); ++backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); ++backend.add(import('@backstage/plugin-techdocs-backend/alpha')); + +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++// auth plugin ++backend.add(import('@backstage/plugin-auth-backend')); ++// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin ++backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); ++// See https://backstage.io/docs/auth/guest/provider + +- return (plugin: string): PluginEnvironment => { +- const logger = root.child({ type: 'plugin', plugin }); +- const database = databaseManager.forPlugin(plugin); +- const cache = cacheManager.forPlugin(plugin); +- return { logger, database, cache, config, reader, discovery }; +- }; +-} ++// catalog plugin ++backend.add(import('@backstage/plugin-catalog-backend/alpha')); ++backend.add( ++ import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ++); + +-async function main() { +- const config = await loadBackendConfig({ +- argv: process.argv, +- logger: getRootLogger(), +- }); +- const createEnv = makeCreateEnv(config); ++// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors ++backend.add(import('@backstage/plugin-catalog-backend-module-logs')); + +- const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); +- const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); +- const authEnv = useHotMemoize(module, () => createEnv('auth')); +- const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); +- const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); +- const searchEnv = useHotMemoize(module, () => createEnv('search')); +- const appEnv = useHotMemoize(module, () => createEnv('app')); ++// permission plugin ++backend.add(import('@backstage/plugin-permission-backend/alpha')); ++// See https://backstage.io/docs/permissions/getting-started for how to create your own permission policy ++backend.add( ++ import('@backstage/plugin-permission-backend-module-allow-all-policy'), ++); + +- const apiRouter = Router(); +- apiRouter.use('/catalog', await catalog(catalogEnv)); +- apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); +- apiRouter.use('/auth', await auth(authEnv)); +- apiRouter.use('/techdocs', await techdocs(techdocsEnv)); +- apiRouter.use('/proxy', await proxy(proxyEnv)); +- apiRouter.use('/search', await search(searchEnv)); +- apiRouter.use(notFoundHandler()); ++// search plugin ++backend.add(import('@backstage/plugin-search-backend/alpha')); + +- const service = createServiceBuilder(module) +- .loadConfig(config) +- .addRouter('/api', apiRouter) +- .addRouter('', await app(appEnv)); ++// search engine ++// See https://backstage.io/docs/features/search/search-engines ++backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); + +- await service.start().catch(err => { +- console.log(err); +- process.exit(1); +- }); +-} ++// search collators ++backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); ++backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); + +-module.hot?.accept(); +-main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); +- process.exit(1); +-}); ++// kubernetes ++backend.add(import('@backstage/plugin-kubernetes-backend/alpha')); ++ ++backend.start(); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +deleted file mode 100644 +index 07fb04f..0000000 +--- a/packages/backend/src/plugins/app.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { createRouter } from '@backstage/plugin-app-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +-}: PluginEnvironment): Promise { +- return await createRouter({ +- logger, +- config, +- appPackageName: 'app', +- }); +-} +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +deleted file mode 100644 +index 5216510..0000000 +--- a/packages/backend/src/plugins/auth.ts ++++ /dev/null +@@ -1,12 +0,0 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, database, discovery }); +-} +diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts +deleted file mode 100644 +index 876cb6b..0000000 +--- a/packages/backend/src/plugins/catalog.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +-import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const builder = await CatalogBuilder.create(env); +- builder.addProcessor(new ScaffolderEntitiesProcessor()); +- const { processingEngine, router } = await builder.build(); +- await processingEngine.start(); +- return router; +-} +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +deleted file mode 100644 +index 506f6d9..0000000 +--- a/packages/backend/src/plugins/proxy.ts ++++ /dev/null +@@ -1,11 +0,0 @@ +-import { createRouter } from '@backstage/plugin-proxy-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); +-} +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +deleted file mode 100644 +index 6be2e97..0000000 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ /dev/null +@@ -1,27 +0,0 @@ +-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'; +-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 }); +- +- return await createRouter({ +- containerRunner, +- logger, +- config, +- database, +- catalogClient, +- reader, +- }); +-} +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +deleted file mode 100644 +index 7fc317d..0000000 +--- a/packages/backend/src/plugins/search.ts ++++ /dev/null +@@ -1,39 +0,0 @@ +-import { useHotCleanup } from '@backstage/backend-common'; +-import { createRouter } from '@backstage/plugin-search-backend'; +-import { +- IndexBuilder, +- LunrSearchEngine, +-} from '@backstage/plugin-search-backend-node'; +-import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +- +-export default async function createPlugin({ +- logger, +- discovery, +- config, +-}: PluginEnvironment) { +- // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); +- +- // Collators are responsible for gathering documents known to plugins. This +- // particular collator gathers entities from the software catalog. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { discovery }), +- }); +- +- // The scheduler controls when documents are gathered from collators and sent +- // to the search engine for indexing. +- const { scheduler } = await indexBuilder.build(); +- +- // 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()); +- +- return await createRouter({ +- engine: indexBuilder.getSearchEngine(), +- logger, +- }); +-} +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +deleted file mode 100644 +index 906d86d..0000000 +--- a/packages/backend/src/plugins/techdocs.ts ++++ /dev/null +@@ -1,53 +0,0 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; +-import { +- createRouter, +- Generators, +- Preparers, +- Publisher, +-} from '@backstage/plugin-techdocs-backend'; +-import Docker from 'dockerode'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +-}: PluginEnvironment): Promise { +- // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, +- }); +- +- // Docker client (conditionally) used by the generators, based on techdocs.generators config. +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- +- // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, +- containerRunner, +- }); +- +- // Publisher is used for +- // 1. Publishing generated files to storage +- // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, +- }); +- +- // checks if the publisher is working and logs the result +- await publisher.getReadiness(); +- +- return await createRouter({ +- preparers, +- generators, +- publisher, +- logger, +- config, +- discovery, +- }); +-} +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +deleted file mode 100644 +index 6c78a2a..0000000 +--- a/packages/backend/src/types.ts ++++ /dev/null +@@ -1,17 +0,0 @@ +-import { Logger } from 'winston'; +-import { Config } from '@backstage/config'; +-import { +- PluginCacheManager, +- PluginDatabaseManager, +- PluginEndpointDiscovery, +- UrlReader, +-} from '@backstage/backend-common'; +- +-export type PluginEnvironment = { +- logger: Logger; +- database: PluginDatabaseManager; +- cache: PluginCacheManager; +- config: Config; +- reader: UrlReader; +- discovery: PluginEndpointDiscovery; +-}; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..733be13 +--- /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 dev', ++ 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..c37fc61 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1,3 @@ ++# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. ++# yarn lockfile v1 ++ diff --git a/diffs/0.4.1..0.5.18.diff b/diffs/0.4.1..0.5.18.diff new file mode 100644 index 00000000..aa9326f2 --- /dev/null +++ b/diffs/0.4.1..0.5.18.diff @@ -0,0 +1,2086 @@ +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..5d426f5 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -1,8 +1,55 @@ + 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/apis/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 ++ ++auth: ++ providers: ++ guest: {} ++ ++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: ++ # Local example data, replace this with your production config, these are intended for demo use only. ++ # File locations are relative to the backend process, typically in a deployed context, such as in a Docker container, this will be the root ++ - type: file ++ target: ./examples/entities.yaml ++ ++ # Local example template ++ - type: file ++ target: ./examples/template/template.yaml ++ rules: ++ - allow: [Template] ++ ++ # Local example organizational data ++ - type: file ++ target: ./examples/org.yaml ++ rules: ++ - allow: [User, Group] +diff --git a/app-config.yaml b/app-config.yaml +index 93b0c3f..ca52ec5 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/integrations/github/locations#configuration + 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 + +@@ -53,49 +66,48 @@ auth: + # see https://backstage.io/docs/auth/ to learn about auth providers +- providers: {} ++ providers: ++ # See https://backstage.io/docs/auth/guest/provider ++ guest: {} + + 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 ++ # 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 ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml ++ rules: ++ - allow: [Template] + +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml ++ rules: ++ - allow: [User, Group] + +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml + +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.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] + +- # 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 +- 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 +- rules: +- - allow: [Template] ++kubernetes: ++ # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options ++ ++# see https://backstage.io/docs/permissions/getting-started for more on the permission framework ++permission: ++ # setting this to `false` will disable permissions ++ enabled: true +diff --git a/backstage.json b/backstage.json +new file mode 100644 +index 0000000..230d39d +--- /dev/null ++++ b/backstage.json +@@ -0,0 +1,3 @@ ++{ ++ "version": "1.30.0" ++} +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..80a963e 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.27.0", ++ "@backstage/e2e-test-utils": "^0.1.1", ++ "@playwright/test": "^1.32.3", ++ "@spotify/prettier-config": "^12.0.0", ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^10.0.0", ++ "prettier": "^2.3.2", ++ "typescript": "~5.4.0" ++ }, ++ "resolutions": { ++ "@types/react": "^18", ++ "@types/react-dom": "^18" + }, +@@ -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..839ff88 +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,27 @@ ++/* ++ * 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('/'); ++ ++ const enterButton = page.getByRole('button', { name: 'Enter' }); ++ await expect(enterButton).toBeVisible(); ++ await enterButton.click(); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 0510279..346ffc1 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,22 +5,37 @@ + "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.5.10", ++ "@backstage/catalog-model": "^1.6.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/core-app-api": "^1.14.2", ++ "@backstage/core-components": "^0.14.10", ++ "@backstage/core-plugin-api": "^1.9.3", ++ "@backstage/integration-react": "^1.1.30", ++ "@backstage/plugin-api-docs": "^0.11.8", ++ "@backstage/plugin-catalog": "^1.22.0", ++ "@backstage/plugin-catalog-common": "^1.0.26", ++ "@backstage/plugin-catalog-graph": "^0.4.8", ++ "@backstage/plugin-catalog-import": "^0.12.2", ++ "@backstage/plugin-catalog-react": "^1.12.3", ++ "@backstage/plugin-kubernetes": "^0.11.13", ++ "@backstage/plugin-org": "^0.6.28", ++ "@backstage/plugin-permission-react": "^0.4.25", ++ "@backstage/plugin-scaffolder": "^1.24.0", ++ "@backstage/plugin-search": "^1.4.15", ++ "@backstage/plugin-search-react": "^1.7.14", ++ "@backstage/plugin-techdocs": "^1.10.8", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.13", ++ "@backstage/plugin-techdocs-react": "^1.2.7", ++ "@backstage/plugin-user-settings": "^0.8.11", ++ "@backstage/theme": "^0.5.6", + "@material-ui/core": "^4.12.2", +@@ -28,30 +43,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": "^18.0.2", ++ "react-dom": "^18.0.2", ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@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", ++ "@backstage/test-utils": "^1.5.10", ++ "@playwright/test": "^1.32.3", ++ "@testing-library/jest-dom": "^6.0.0", ++ "@testing-library/react": "^14.0.0", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^9.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..18da7c4 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -8,5 +8,4 @@ + name="description" +- content="Backstage is an open platform for building developer portals" ++ content="Backstage is an open source framework for building developer portals" + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index 82bc479..ec8ba1d 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { renderWithEffects } from '@backstage/test-utils'; ++import { render, waitFor } from '@testing-library/react'; + import App from './App'; +@@ -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', + }, +@@ -22,4 +22,7 @@ describe('App', () => { + +- const rendered = await renderWithEffects(); +- expect(rendered.baseElement).toBeInTheDocument(); ++ const rendered = render(); ++ ++ await waitFor(() => { ++ expect(rendered.baseElement).toBeInTheDocument(); ++ }); + }); +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 4cd8368..e24c468 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,6 +13,5 @@ import { + import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; ++import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +-import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -21,2 +20,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'; +@@ -27,4 +28,12 @@ import { Root } from './components/Root'; + +-import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; +-import { createApp, FlatRoutes } from '@backstage/core-app-api'; ++import { ++ AlertDisplay, ++ OAuthRequestDialog, ++ SignInPage, ++} from '@backstage/core-components'; ++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 +45,6 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); + bind(apiDocsPlugin.externalRoutes, { +- createComponent: scaffolderPlugin.routes.root, ++ registerApi: catalogImportPlugin.routes.importPage, + }); +@@ -42,3 +52,10 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); ++ bind(orgPlugin.externalRoutes, { ++ catalogIndex: catalogPlugin.routes.catalogIndex, ++ }); ++ }, ++ components: { ++ SignInPage: props => , + }, +@@ -46,8 +63,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -59,5 +73,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -69,6 +85,9 @@ const routes = ( + } ++ path="/catalog-import" ++ element={ ++ ++ ++ ++ } + /> +- } /> + }> +@@ -77,2 +96,3 @@ const routes = ( + } /> ++ } /> + +@@ -80,4 +100,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -87,5 +107,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..853d175 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,22 +1,5 @@ +-/* +- * 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'; + import ExtensionIcon from '@material-ui/icons/Extension'; +-import MapIcon from '@material-ui/icons/MyLocation'; + import LibraryBooks from '@material-ui/icons/LibraryBooks'; +@@ -25,15 +8,23 @@ 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'; ++import { MyGroupsSidebarItem } from '@backstage/plugin-org'; ++import GroupIcon from '@material-ui/icons/People'; + +@@ -56,3 +47,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -60,8 +51,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -76,17 +62,32 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ } to="/search"> ++ ++ + +- {/* Global nav, not org-specific */} +- +- +- +- +- {/* End global nav */} +- +- +- +- ++ }> ++ {/* Global nav, not org-specific */} ++ ++ ++ ++ ++ ++ {/* End global nav */} ++ ++ ++ {/* Items in this group will be scrollable if they run out of space */} ++ ++ + + +- ++ } ++ 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..6164c76 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,7 +27,5 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +-import { +- isGithubActionsAvailable, +- EntityGithubActionsContent, +-} from '@backstage/plugin-github-actions'; + import { +@@ -56,2 +38,32 @@ 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'; ++ ++import { ++ EntityKubernetesContent, ++ isKubernetesAvailable, ++} from '@backstage/plugin-kubernetes'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); + +@@ -61,5 +73,9 @@ const cicdContent = ( + +- +- +- ++ {/* ++ Here you can add support for different CI/CD services, for example ++ using @backstage-community/plugin-github-actions as follows: ++ ++ ++ ++ */} + +@@ -94,2 +110,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -110,2 +134,6 @@ const overviewContent = ( + ++ ++ ++ ++ + +@@ -129,2 +157,10 @@ const serviceEntityPage = ( + ++ ++ ++ ++ + +@@ -152,3 +188,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -167,2 +203,10 @@ const websiteEntityPage = ( + ++ ++ ++ ++ + +@@ -179,3 +223,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -198,3 +242,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -225,2 +269,5 @@ const apiPage = ( + ++ ++ ++ + +@@ -276,5 +323,8 @@ const groupPage = ( + +- ++ + + ++ ++ ++ + +@@ -292,3 +342,9 @@ const systemPage = ( + +- ++ ++ ++ ++ ++ ++ ++ + +@@ -304,3 +360,19 @@ const systemPage = ( + +- ++ + +@@ -317,2 +389,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/app/src/index.tsx b/packages/app/src/index.tsx +index b16aaf7..d875c77 100644 +--- a/packages/app/src/index.tsx ++++ b/packages/app/src/index.tsx +@@ -2,5 +2,5 @@ import '@backstage/cli/asset-types'; + import React from 'react'; +-import ReactDOM from 'react-dom'; ++import ReactDOM from 'react-dom/client'; + import App from './App'; + +-ReactDOM.render(, document.getElementById('root')); ++ReactDOM.createRoot(document.getElementById('root')!).render(); +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..bef67b5 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,15 @@ 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 ++ ++# This will include the examples, if you don't need these simply remove this line ++COPY --chown=node:node examples ./examples + + # 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..6a14abc 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,32 +6,43 @@ + "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", +- "express": "^4.17.1", +- "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "@backstage/backend-common": "^0.24.0", ++ "@backstage/backend-defaults": "^0.4.2", ++ "@backstage/backend-tasks": "^0.6.0", ++ "@backstage/config": "^1.2.0", ++ "@backstage/plugin-app-backend": "^0.3.72", ++ "@backstage/plugin-auth-backend": "^0.22.10", ++ "@backstage/plugin-auth-backend-module-github-provider": "^0.1.20", ++ "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.9", ++ "@backstage/plugin-auth-node": "^0.5.0", ++ "@backstage/plugin-catalog-backend": "^1.25.0", ++ "@backstage/plugin-catalog-backend-module-logs": "^0.0.2", ++ "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.21", ++ "@backstage/plugin-kubernetes-backend": "^0.18.4", ++ "@backstage/plugin-permission-backend": "^0.5.47", ++ "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.1.20", ++ "@backstage/plugin-permission-common": "^0.8.1", ++ "@backstage/plugin-permission-node": "^0.8.1", ++ "@backstage/plugin-proxy-backend": "^0.5.4", ++ "@backstage/plugin-scaffolder-backend": "^1.24.0", ++ "@backstage/plugin-search-backend": "^1.5.15", ++ "@backstage/plugin-search-backend-module-catalog": "^0.2.0", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.33", ++ "@backstage/plugin-search-backend-module-techdocs": "^0.2.0", ++ "@backstage/plugin-search-backend-node": "^1.3.0", ++ "@backstage/plugin-techdocs-backend": "^1.10.10", ++ "app": "link:../app", ++ "better-sqlite3": "^9.0.0", ++ "node-gyp": "^10.0.0", ++ "pg": "^8.11.3", + "winston": "^3.2.1" +@@ -39,6 +50,6 @@ + "devDependencies": { +- "@backstage/cli": "^0.8.1", +- "@types/dockerode": "^3.2.1", ++ "@backstage/cli": "^0.27.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.test.ts b/packages/backend/src/index.test.ts +deleted file mode 100644 +index 7814b8c..0000000 +--- a/packages/backend/src/index.test.ts ++++ /dev/null +@@ -1,8 +0,0 @@ +-import { PluginEnvironment } from './types'; +- +-describe('test', () => { +- it('unbreaks the test runner', () => { +- const unbreaker = {} as PluginEnvironment; +- expect(unbreaker).toBeTruthy(); +- }); +-}); +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index f2b14b2..f57d4c5 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -8,81 +8,47 @@ + +-import Router from 'express-promise-router'; +-import { +- createServiceBuilder, +- loadBackendConfig, +- getRootLogger, +- useHotMemoize, +- notFoundHandler, +- CacheManager, +- DatabaseManager, +- SingleHostDiscovery, +- UrlReaders, +-} from '@backstage/backend-common'; +-import { Config } from '@backstage/config'; +-import app from './plugins/app'; +-import auth from './plugins/auth'; +-import catalog from './plugins/catalog'; +-import scaffolder from './plugins/scaffolder'; +-import proxy from './plugins/proxy'; +-import techdocs from './plugins/techdocs'; +-import search from './plugins/search'; +-import { PluginEnvironment } from './types'; ++import { createBackend } from '@backstage/backend-defaults'; + +-function makeCreateEnv(config: Config) { +- const root = getRootLogger(); +- const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++const backend = createBackend(); + +- root.info(`Created UrlReader ${reader}`); ++backend.add(import('@backstage/plugin-app-backend/alpha')); ++backend.add(import('@backstage/plugin-proxy-backend/alpha')); ++backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); ++backend.add(import('@backstage/plugin-techdocs-backend/alpha')); + +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++// auth plugin ++backend.add(import('@backstage/plugin-auth-backend')); ++// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin ++backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); ++// See https://backstage.io/docs/auth/guest/provider + +- return (plugin: string): PluginEnvironment => { +- const logger = root.child({ type: 'plugin', plugin }); +- const database = databaseManager.forPlugin(plugin); +- const cache = cacheManager.forPlugin(plugin); +- return { logger, database, cache, config, reader, discovery }; +- }; +-} ++// catalog plugin ++backend.add(import('@backstage/plugin-catalog-backend/alpha')); ++backend.add( ++ import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ++); + +-async function main() { +- const config = await loadBackendConfig({ +- argv: process.argv, +- logger: getRootLogger(), +- }); +- const createEnv = makeCreateEnv(config); ++// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors ++backend.add(import('@backstage/plugin-catalog-backend-module-logs')); + +- const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); +- const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); +- const authEnv = useHotMemoize(module, () => createEnv('auth')); +- const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); +- const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); +- const searchEnv = useHotMemoize(module, () => createEnv('search')); +- const appEnv = useHotMemoize(module, () => createEnv('app')); ++// permission plugin ++backend.add(import('@backstage/plugin-permission-backend/alpha')); ++// See https://backstage.io/docs/permissions/getting-started for how to create your own permission policy ++backend.add( ++ import('@backstage/plugin-permission-backend-module-allow-all-policy'), ++); + +- const apiRouter = Router(); +- apiRouter.use('/catalog', await catalog(catalogEnv)); +- apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); +- apiRouter.use('/auth', await auth(authEnv)); +- apiRouter.use('/techdocs', await techdocs(techdocsEnv)); +- apiRouter.use('/proxy', await proxy(proxyEnv)); +- apiRouter.use('/search', await search(searchEnv)); +- apiRouter.use(notFoundHandler()); ++// search plugin ++backend.add(import('@backstage/plugin-search-backend/alpha')); + +- const service = createServiceBuilder(module) +- .loadConfig(config) +- .addRouter('/api', apiRouter) +- .addRouter('', await app(appEnv)); ++// search engine ++// See https://backstage.io/docs/features/search/search-engines ++backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); + +- await service.start().catch(err => { +- console.log(err); +- process.exit(1); +- }); +-} ++// search collators ++backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); ++backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); + +-module.hot?.accept(); +-main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); +- process.exit(1); +-}); ++// kubernetes ++backend.add(import('@backstage/plugin-kubernetes-backend/alpha')); ++ ++backend.start(); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +deleted file mode 100644 +index 07fb04f..0000000 +--- a/packages/backend/src/plugins/app.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { createRouter } from '@backstage/plugin-app-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +-}: PluginEnvironment): Promise { +- return await createRouter({ +- logger, +- config, +- appPackageName: 'app', +- }); +-} +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +deleted file mode 100644 +index 5216510..0000000 +--- a/packages/backend/src/plugins/auth.ts ++++ /dev/null +@@ -1,12 +0,0 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, database, discovery }); +-} +diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts +deleted file mode 100644 +index 876cb6b..0000000 +--- a/packages/backend/src/plugins/catalog.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +-import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const builder = await CatalogBuilder.create(env); +- builder.addProcessor(new ScaffolderEntitiesProcessor()); +- const { processingEngine, router } = await builder.build(); +- await processingEngine.start(); +- return router; +-} +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +deleted file mode 100644 +index 506f6d9..0000000 +--- a/packages/backend/src/plugins/proxy.ts ++++ /dev/null +@@ -1,11 +0,0 @@ +-import { createRouter } from '@backstage/plugin-proxy-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); +-} +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +deleted file mode 100644 +index 6be2e97..0000000 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ /dev/null +@@ -1,27 +0,0 @@ +-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'; +-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 }); +- +- return await createRouter({ +- containerRunner, +- logger, +- config, +- database, +- catalogClient, +- reader, +- }); +-} +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +deleted file mode 100644 +index 7fc317d..0000000 +--- a/packages/backend/src/plugins/search.ts ++++ /dev/null +@@ -1,39 +0,0 @@ +-import { useHotCleanup } from '@backstage/backend-common'; +-import { createRouter } from '@backstage/plugin-search-backend'; +-import { +- IndexBuilder, +- LunrSearchEngine, +-} from '@backstage/plugin-search-backend-node'; +-import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +- +-export default async function createPlugin({ +- logger, +- discovery, +- config, +-}: PluginEnvironment) { +- // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); +- +- // Collators are responsible for gathering documents known to plugins. This +- // particular collator gathers entities from the software catalog. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { discovery }), +- }); +- +- // The scheduler controls when documents are gathered from collators and sent +- // to the search engine for indexing. +- const { scheduler } = await indexBuilder.build(); +- +- // 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()); +- +- return await createRouter({ +- engine: indexBuilder.getSearchEngine(), +- logger, +- }); +-} +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +deleted file mode 100644 +index 906d86d..0000000 +--- a/packages/backend/src/plugins/techdocs.ts ++++ /dev/null +@@ -1,53 +0,0 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; +-import { +- createRouter, +- Generators, +- Preparers, +- Publisher, +-} from '@backstage/plugin-techdocs-backend'; +-import Docker from 'dockerode'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +-}: PluginEnvironment): Promise { +- // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, +- }); +- +- // Docker client (conditionally) used by the generators, based on techdocs.generators config. +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- +- // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, +- containerRunner, +- }); +- +- // Publisher is used for +- // 1. Publishing generated files to storage +- // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, +- }); +- +- // checks if the publisher is working and logs the result +- await publisher.getReadiness(); +- +- return await createRouter({ +- preparers, +- generators, +- publisher, +- logger, +- config, +- discovery, +- }); +-} +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +deleted file mode 100644 +index 6c78a2a..0000000 +--- a/packages/backend/src/types.ts ++++ /dev/null +@@ -1,17 +0,0 @@ +-import { Logger } from 'winston'; +-import { Config } from '@backstage/config'; +-import { +- PluginCacheManager, +- PluginDatabaseManager, +- PluginEndpointDiscovery, +- UrlReader, +-} from '@backstage/backend-common'; +- +-export type PluginEnvironment = { +- logger: Logger; +- database: PluginDatabaseManager; +- cache: PluginCacheManager; +- config: Config; +- reader: UrlReader; +- discovery: PluginEndpointDiscovery; +-}; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..733be13 +--- /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 dev', ++ 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..c37fc61 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1,3 @@ ++# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. ++# yarn lockfile v1 ++ diff --git a/diffs/0.4.10..0.5.18.diff b/diffs/0.4.10..0.5.18.diff new file mode 100644 index 00000000..0b10e262 --- /dev/null +++ b/diffs/0.4.10..0.5.18.diff @@ -0,0 +1,2066 @@ +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..5d426f5 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,51 @@ 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/apis/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 ++ ++auth: ++ providers: ++ guest: {} ++ ++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: ++ # Local example data, replace this with your production config, these are intended for demo use only. ++ # File locations are relative to the backend process, typically in a deployed context, such as in a Docker container, this will be the root ++ - type: file ++ target: ./examples/entities.yaml ++ ++ # Local example template ++ - type: file ++ target: ./examples/template/template.yaml ++ rules: ++ - allow: [Template] ++ ++ # Local example organizational data ++ - type: file ++ target: ./examples/org.yaml ++ rules: ++ - allow: [User, Group] +diff --git a/app-config.yaml b/app-config.yaml +index 1a622a2..ca52ec5 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/integrations/github/locations#configuration + 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 + +@@ -58,3 +66,5 @@ auth: + # see https://backstage.io/docs/auth/ to learn about auth providers +- providers: {} ++ providers: ++ # See https://backstage.io/docs/auth/guest/provider ++ guest: {} + +@@ -64,41 +74,40 @@ 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 ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml ++ rules: ++ - allow: [Template] + +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml ++ rules: ++ - allow: [User, Group] + +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml + +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.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] + +- # 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 +- 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 +- rules: +- - allow: [Template] ++kubernetes: ++ # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options ++ ++# see https://backstage.io/docs/permissions/getting-started for more on the permission framework ++permission: ++ # setting this to `false` will disable permissions ++ enabled: true +diff --git a/backstage.json b/backstage.json +index da31324..230d39d 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "0.4.10" ++ "version": "1.30.0" + } +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..80a963e 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.27.0", ++ "@backstage/e2e-test-utils": "^0.1.1", ++ "@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": "^10.0.0", ++ "prettier": "^2.3.2", ++ "typescript": "~5.4.0" ++ }, ++ "resolutions": { ++ "@types/react": "^18", ++ "@types/react-dom": "^18" + }, +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..839ff88 +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,27 @@ ++/* ++ * 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('/'); ++ ++ const enterButton = page.getByRole('button', { name: 'Enter' }); ++ await expect(enterButton).toBeVisible(); ++ await enterButton.click(); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 9b9ea2f..346ffc1 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,22 +5,37 @@ + "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.5.10", ++ "@backstage/catalog-model": "^1.6.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/core-app-api": "^1.14.2", ++ "@backstage/core-components": "^0.14.10", ++ "@backstage/core-plugin-api": "^1.9.3", ++ "@backstage/integration-react": "^1.1.30", ++ "@backstage/plugin-api-docs": "^0.11.8", ++ "@backstage/plugin-catalog": "^1.22.0", ++ "@backstage/plugin-catalog-common": "^1.0.26", ++ "@backstage/plugin-catalog-graph": "^0.4.8", ++ "@backstage/plugin-catalog-import": "^0.12.2", ++ "@backstage/plugin-catalog-react": "^1.12.3", ++ "@backstage/plugin-kubernetes": "^0.11.13", ++ "@backstage/plugin-org": "^0.6.28", ++ "@backstage/plugin-permission-react": "^0.4.25", ++ "@backstage/plugin-scaffolder": "^1.24.0", ++ "@backstage/plugin-search": "^1.4.15", ++ "@backstage/plugin-search-react": "^1.7.14", ++ "@backstage/plugin-techdocs": "^1.10.8", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.13", ++ "@backstage/plugin-techdocs-react": "^1.2.7", ++ "@backstage/plugin-user-settings": "^0.8.11", ++ "@backstage/theme": "^0.5.6", + "@material-ui/core": "^4.12.2", +@@ -28,31 +43,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": "^18.0.2", ++ "react-dom": "^18.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", +- "@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", ++ "@backstage/test-utils": "^1.5.10", ++ "@playwright/test": "^1.32.3", ++ "@testing-library/jest-dom": "^6.0.0", ++ "@testing-library/react": "^14.0.0", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^9.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..18da7c4 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -8,5 +8,4 @@ + name="description" +- content="Backstage is an open platform for building developer portals" ++ content="Backstage is an open source framework for building developer portals" + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index b94cac7..ec8ba1d 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { renderWithEffects } from '@backstage/test-utils'; ++import { render, waitFor } from '@testing-library/react'; + import App from './App'; +@@ -22,4 +22,7 @@ describe('App', () => { + +- const rendered = await renderWithEffects(); +- expect(rendered.baseElement).toBeInTheDocument(); ++ const rendered = render(); ++ ++ await waitFor(() => { ++ expect(rendered.baseElement).toBeInTheDocument(); ++ }); + }); +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 8a53583..e24c468 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,6 +13,5 @@ import { + import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; ++import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +-import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -21,2 +20,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'; +@@ -27,5 +28,12 @@ import { Root } from './components/Root'; + +-import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; ++import { ++ AlertDisplay, ++ OAuthRequestDialog, ++ SignInPage, ++} 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 +45,6 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); + bind(apiDocsPlugin.externalRoutes, { +- createComponent: scaffolderPlugin.routes.root, ++ registerApi: catalogImportPlugin.routes.importPage, + }); +@@ -43,3 +52,10 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); ++ bind(orgPlugin.externalRoutes, { ++ catalogIndex: catalogPlugin.routes.catalogIndex, ++ }); ++ }, ++ components: { ++ SignInPage: props => , + }, +@@ -47,8 +63,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -60,5 +73,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -70,6 +85,9 @@ const routes = ( + } ++ path="/catalog-import" ++ element={ ++ ++ ++ ++ } + /> +- } /> + }> +@@ -78,2 +96,3 @@ const routes = ( + } /> ++ } /> + +@@ -81,4 +100,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -88,5 +107,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..853d175 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,22 +1,5 @@ +-/* +- * 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'; + import ExtensionIcon from '@material-ui/icons/Extension'; +-import MapIcon from '@material-ui/icons/MyLocation'; + import LibraryBooks from '@material-ui/icons/LibraryBooks'; +@@ -25,18 +8,23 @@ 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'; ++import { MyGroupsSidebarItem } from '@backstage/plugin-org'; ++import GroupIcon from '@material-ui/icons/People'; + +@@ -59,3 +47,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -63,8 +51,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -79,19 +62,32 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ } to="/search"> + +- ++ + +- {/* Global nav, not org-specific */} +- +- +- +- +- {/* End global nav */} +- +- +- +- ++ }> ++ {/* Global nav, not org-specific */} ++ ++ ++ ++ ++ ++ {/* End global nav */} ++ ++ ++ {/* Items in this group will be scrollable if they run out of space */} ++ ++ + + +- ++ } ++ 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..6164c76 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,7 +27,5 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +-import { +- isGithubActionsAvailable, +- EntityGithubActionsContent, +-} from '@backstage/plugin-github-actions'; + import { +@@ -56,2 +38,32 @@ 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'; ++ ++import { ++ EntityKubernetesContent, ++ isKubernetesAvailable, ++} from '@backstage/plugin-kubernetes'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); + +@@ -61,5 +73,9 @@ const cicdContent = ( + +- +- +- ++ {/* ++ Here you can add support for different CI/CD services, for example ++ using @backstage-community/plugin-github-actions as follows: ++ ++ ++ ++ */} + +@@ -94,2 +110,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -110,2 +134,6 @@ const overviewContent = ( + ++ ++ ++ ++ + +@@ -129,2 +157,10 @@ const serviceEntityPage = ( + ++ ++ ++ ++ + +@@ -152,3 +188,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -167,2 +203,10 @@ const websiteEntityPage = ( + ++ ++ ++ ++ + +@@ -179,3 +223,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -198,3 +242,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -225,2 +269,5 @@ const apiPage = ( + ++ ++ ++ + +@@ -276,5 +323,8 @@ const groupPage = ( + +- ++ + + ++ ++ ++ + +@@ -292,3 +342,9 @@ const systemPage = ( + +- ++ ++ ++ ++ ++ ++ ++ + +@@ -304,3 +360,19 @@ const systemPage = ( + +- ++ + +@@ -317,2 +389,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/app/src/index.tsx b/packages/app/src/index.tsx +index b16aaf7..d875c77 100644 +--- a/packages/app/src/index.tsx ++++ b/packages/app/src/index.tsx +@@ -2,5 +2,5 @@ import '@backstage/cli/asset-types'; + import React from 'react'; +-import ReactDOM from 'react-dom'; ++import ReactDOM from 'react-dom/client'; + import App from './App'; + +-ReactDOM.render(, document.getElementById('root')); ++ReactDOM.createRoot(document.getElementById('root')!).render(); +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..bef67b5 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,15 @@ 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 ++ ++# This will include the examples, if you don't need these simply remove this line ++COPY --chown=node:node examples ./examples + + # 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..6a14abc 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,33 +6,43 @@ + "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", +- "dockerode": "^3.3.1", +- "express": "^4.17.1", +- "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "@backstage/backend-common": "^0.24.0", ++ "@backstage/backend-defaults": "^0.4.2", ++ "@backstage/backend-tasks": "^0.6.0", ++ "@backstage/config": "^1.2.0", ++ "@backstage/plugin-app-backend": "^0.3.72", ++ "@backstage/plugin-auth-backend": "^0.22.10", ++ "@backstage/plugin-auth-backend-module-github-provider": "^0.1.20", ++ "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.9", ++ "@backstage/plugin-auth-node": "^0.5.0", ++ "@backstage/plugin-catalog-backend": "^1.25.0", ++ "@backstage/plugin-catalog-backend-module-logs": "^0.0.2", ++ "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.21", ++ "@backstage/plugin-kubernetes-backend": "^0.18.4", ++ "@backstage/plugin-permission-backend": "^0.5.47", ++ "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.1.20", ++ "@backstage/plugin-permission-common": "^0.8.1", ++ "@backstage/plugin-permission-node": "^0.8.1", ++ "@backstage/plugin-proxy-backend": "^0.5.4", ++ "@backstage/plugin-scaffolder-backend": "^1.24.0", ++ "@backstage/plugin-search-backend": "^1.5.15", ++ "@backstage/plugin-search-backend-module-catalog": "^0.2.0", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.33", ++ "@backstage/plugin-search-backend-module-techdocs": "^0.2.0", ++ "@backstage/plugin-search-backend-node": "^1.3.0", ++ "@backstage/plugin-techdocs-backend": "^1.10.10", ++ "app": "link:../app", ++ "better-sqlite3": "^9.0.0", ++ "node-gyp": "^10.0.0", ++ "pg": "^8.11.3", + "winston": "^3.2.1" +@@ -40,6 +50,6 @@ + "devDependencies": { +- "@backstage/cli": "^0.10.4", +- "@types/dockerode": "^3.3.0", ++ "@backstage/cli": "^0.27.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.test.ts b/packages/backend/src/index.test.ts +deleted file mode 100644 +index 7814b8c..0000000 +--- a/packages/backend/src/index.test.ts ++++ /dev/null +@@ -1,8 +0,0 @@ +-import { PluginEnvironment } from './types'; +- +-describe('test', () => { +- it('unbreaks the test runner', () => { +- const unbreaker = {} as PluginEnvironment; +- expect(unbreaker).toBeTruthy(); +- }); +-}); +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 08d21e6..f57d4c5 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -8,96 +8,47 @@ + +-import Router from 'express-promise-router'; +-import { +- createServiceBuilder, +- loadBackendConfig, +- getRootLogger, +- useHotMemoize, +- notFoundHandler, +- CacheManager, +- DatabaseManager, +- SingleHostDiscovery, +- UrlReaders, +- ServerTokenManager, +-} from '@backstage/backend-common'; +-import { TaskScheduler } from '@backstage/backend-tasks'; +-import { Config } from '@backstage/config'; +-import app from './plugins/app'; +-import auth from './plugins/auth'; +-import catalog from './plugins/catalog'; +-import scaffolder from './plugins/scaffolder'; +-import proxy from './plugins/proxy'; +-import techdocs from './plugins/techdocs'; +-import search from './plugins/search'; +-import { PluginEnvironment } from './types'; ++import { createBackend } from '@backstage/backend-defaults'; + +-function makeCreateEnv(config: Config) { +- const root = getRootLogger(); +- const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); +- const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); ++const backend = createBackend(); + +- root.info(`Created UrlReader ${reader}`); ++backend.add(import('@backstage/plugin-app-backend/alpha')); ++backend.add(import('@backstage/plugin-proxy-backend/alpha')); ++backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); ++backend.add(import('@backstage/plugin-techdocs-backend/alpha')); + +- return (plugin: string): PluginEnvironment => { +- const logger = root.child({ type: 'plugin', plugin }); +- const database = databaseManager.forPlugin(plugin); +- const cache = cacheManager.forPlugin(plugin); +- const scheduler = taskScheduler.forPlugin(plugin); +- return { +- logger, +- database, +- cache, +- config, +- reader, +- discovery, +- tokenManager, +- scheduler, +- }; +- }; +-} ++// auth plugin ++backend.add(import('@backstage/plugin-auth-backend')); ++// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin ++backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); ++// See https://backstage.io/docs/auth/guest/provider + +-async function main() { +- const config = await loadBackendConfig({ +- argv: process.argv, +- logger: getRootLogger(), +- }); +- const createEnv = makeCreateEnv(config); ++// catalog plugin ++backend.add(import('@backstage/plugin-catalog-backend/alpha')); ++backend.add( ++ import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ++); + +- const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); +- const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); +- const authEnv = useHotMemoize(module, () => createEnv('auth')); +- const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); +- const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); +- const searchEnv = useHotMemoize(module, () => createEnv('search')); +- const appEnv = useHotMemoize(module, () => createEnv('app')); ++// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors ++backend.add(import('@backstage/plugin-catalog-backend-module-logs')); + +- const apiRouter = Router(); +- apiRouter.use('/catalog', await catalog(catalogEnv)); +- apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); +- apiRouter.use('/auth', await auth(authEnv)); +- apiRouter.use('/techdocs', await techdocs(techdocsEnv)); +- apiRouter.use('/proxy', await proxy(proxyEnv)); +- apiRouter.use('/search', await search(searchEnv)); ++// permission plugin ++backend.add(import('@backstage/plugin-permission-backend/alpha')); ++// See https://backstage.io/docs/permissions/getting-started for how to create your own permission policy ++backend.add( ++ import('@backstage/plugin-permission-backend-module-allow-all-policy'), ++); + +- // Add backends ABOVE this line; this 404 handler is the catch-all fallback +- apiRouter.use(notFoundHandler()); ++// search plugin ++backend.add(import('@backstage/plugin-search-backend/alpha')); + +- const service = createServiceBuilder(module) +- .loadConfig(config) +- .addRouter('/api', apiRouter) +- .addRouter('', await app(appEnv)); ++// search engine ++// See https://backstage.io/docs/features/search/search-engines ++backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); + +- await service.start().catch(err => { +- console.log(err); +- process.exit(1); +- }); +-} ++// search collators ++backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); ++backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); + +-module.hot?.accept(); +-main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); +- process.exit(1); +-}); ++// kubernetes ++backend.add(import('@backstage/plugin-kubernetes-backend/alpha')); ++ ++backend.start(); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +deleted file mode 100644 +index 07fb04f..0000000 +--- a/packages/backend/src/plugins/app.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { createRouter } from '@backstage/plugin-app-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +-}: PluginEnvironment): Promise { +- return await createRouter({ +- logger, +- config, +- appPackageName: 'app', +- }); +-} +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +deleted file mode 100644 +index 5216510..0000000 +--- a/packages/backend/src/plugins/auth.ts ++++ /dev/null +@@ -1,12 +0,0 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, database, discovery }); +-} +diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts +deleted file mode 100644 +index 876cb6b..0000000 +--- a/packages/backend/src/plugins/catalog.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +-import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const builder = await CatalogBuilder.create(env); +- builder.addProcessor(new ScaffolderEntitiesProcessor()); +- const { processingEngine, router } = await builder.build(); +- await processingEngine.start(); +- return router; +-} +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +deleted file mode 100644 +index 506f6d9..0000000 +--- a/packages/backend/src/plugins/proxy.ts ++++ /dev/null +@@ -1,11 +0,0 @@ +-import { createRouter } from '@backstage/plugin-proxy-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); +-} +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +deleted file mode 100644 +index 6be2e97..0000000 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ /dev/null +@@ -1,27 +0,0 @@ +-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'; +-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 }); +- +- return await createRouter({ +- containerRunner, +- logger, +- config, +- database, +- catalogClient, +- reader, +- }); +-} +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +deleted file mode 100644 +index f23b0c7..0000000 +--- a/packages/backend/src/plugins/search.ts ++++ /dev/null +@@ -1,54 +0,0 @@ +-import { useHotCleanup } from '@backstage/backend-common'; +-import { createRouter } from '@backstage/plugin-search-backend'; +-import { +- IndexBuilder, +- LunrSearchEngine, +-} from '@backstage/plugin-search-backend-node'; +-import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend'; +- +-export default async function createPlugin({ +- logger, +- discovery, +- config, +- tokenManager, +-}: PluginEnvironment) { +- // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); +- +- // Collators are responsible for gathering documents known to plugins. This +- // collator gathers entities from the software catalog. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { +- discovery, +- tokenManager, +- }), +- }); +- +- // collator gathers entities from techdocs. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultTechDocsCollator.fromConfig(config, { +- discovery, +- logger, +- tokenManager, +- }), +- }); +- +- // The scheduler controls when documents are gathered from collators and sent +- // to the search engine for indexing. +- const { scheduler } = await indexBuilder.build(); +- +- // 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()); +- +- return await createRouter({ +- engine: indexBuilder.getSearchEngine(), +- logger, +- }); +-} +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +deleted file mode 100644 +index 054c64d..0000000 +--- a/packages/backend/src/plugins/techdocs.ts ++++ /dev/null +@@ -1,55 +0,0 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; +-import { +- createRouter, +- Generators, +- Preparers, +- Publisher, +-} from '@backstage/plugin-techdocs-backend'; +-import Docker from 'dockerode'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +- cache, +-}: PluginEnvironment): Promise { +- // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, +- }); +- +- // Docker client (conditionally) used by the generators, based on techdocs.generators config. +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- +- // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, +- containerRunner, +- }); +- +- // Publisher is used for +- // 1. Publishing generated files to storage +- // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, +- }); +- +- // checks if the publisher is working and logs the result +- await publisher.getReadiness(); +- +- return await createRouter({ +- preparers, +- generators, +- publisher, +- logger, +- config, +- discovery, +- cache, +- }); +-} +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +deleted file mode 100644 +index c3d0158..0000000 +--- a/packages/backend/src/types.ts ++++ /dev/null +@@ -1,21 +0,0 @@ +-import { Logger } from 'winston'; +-import { Config } from '@backstage/config'; +-import { +- PluginCacheManager, +- PluginDatabaseManager, +- PluginEndpointDiscovery, +- TokenManager, +- UrlReader, +-} from '@backstage/backend-common'; +-import { PluginTaskScheduler } from '@backstage/backend-tasks'; +- +-export type PluginEnvironment = { +- logger: Logger; +- database: PluginDatabaseManager; +- cache: PluginCacheManager; +- config: Config; +- reader: UrlReader; +- discovery: PluginEndpointDiscovery; +- tokenManager: TokenManager; +- scheduler: PluginTaskScheduler; +-}; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..733be13 +--- /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 dev', ++ 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..c37fc61 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1,3 @@ ++# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. ++# yarn lockfile v1 ++ diff --git a/diffs/0.4.11..0.5.18.diff b/diffs/0.4.11..0.5.18.diff new file mode 100644 index 00000000..de51e7a0 --- /dev/null +++ b/diffs/0.4.11..0.5.18.diff @@ -0,0 +1,2066 @@ +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..5d426f5 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,51 @@ 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/apis/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 ++ ++auth: ++ providers: ++ guest: {} ++ ++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: ++ # Local example data, replace this with your production config, these are intended for demo use only. ++ # File locations are relative to the backend process, typically in a deployed context, such as in a Docker container, this will be the root ++ - type: file ++ target: ./examples/entities.yaml ++ ++ # Local example template ++ - type: file ++ target: ./examples/template/template.yaml ++ rules: ++ - allow: [Template] ++ ++ # Local example organizational data ++ - type: file ++ target: ./examples/org.yaml ++ rules: ++ - allow: [User, Group] +diff --git a/app-config.yaml b/app-config.yaml +index 1a622a2..ca52ec5 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/integrations/github/locations#configuration + 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 + +@@ -58,3 +66,5 @@ auth: + # see https://backstage.io/docs/auth/ to learn about auth providers +- providers: {} ++ providers: ++ # See https://backstage.io/docs/auth/guest/provider ++ guest: {} + +@@ -64,41 +74,40 @@ 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 ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml ++ rules: ++ - allow: [Template] + +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml ++ rules: ++ - allow: [User, Group] + +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml + +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.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] + +- # 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 +- 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 +- rules: +- - allow: [Template] ++kubernetes: ++ # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options ++ ++# see https://backstage.io/docs/permissions/getting-started for more on the permission framework ++permission: ++ # setting this to `false` will disable permissions ++ enabled: true +diff --git a/backstage.json b/backstage.json +index 35313e0..230d39d 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "0.4.11" ++ "version": "1.30.0" + } +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..80a963e 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.27.0", ++ "@backstage/e2e-test-utils": "^0.1.1", ++ "@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": "^10.0.0", ++ "prettier": "^2.3.2", ++ "typescript": "~5.4.0" ++ }, ++ "resolutions": { ++ "@types/react": "^18", ++ "@types/react-dom": "^18" + }, +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..839ff88 +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,27 @@ ++/* ++ * 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('/'); ++ ++ const enterButton = page.getByRole('button', { name: 'Enter' }); ++ await expect(enterButton).toBeVisible(); ++ await enterButton.click(); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index a077d5f..346ffc1 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,22 +5,37 @@ + "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.5.10", ++ "@backstage/catalog-model": "^1.6.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/core-app-api": "^1.14.2", ++ "@backstage/core-components": "^0.14.10", ++ "@backstage/core-plugin-api": "^1.9.3", ++ "@backstage/integration-react": "^1.1.30", ++ "@backstage/plugin-api-docs": "^0.11.8", ++ "@backstage/plugin-catalog": "^1.22.0", ++ "@backstage/plugin-catalog-common": "^1.0.26", ++ "@backstage/plugin-catalog-graph": "^0.4.8", ++ "@backstage/plugin-catalog-import": "^0.12.2", ++ "@backstage/plugin-catalog-react": "^1.12.3", ++ "@backstage/plugin-kubernetes": "^0.11.13", ++ "@backstage/plugin-org": "^0.6.28", ++ "@backstage/plugin-permission-react": "^0.4.25", ++ "@backstage/plugin-scaffolder": "^1.24.0", ++ "@backstage/plugin-search": "^1.4.15", ++ "@backstage/plugin-search-react": "^1.7.14", ++ "@backstage/plugin-techdocs": "^1.10.8", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.13", ++ "@backstage/plugin-techdocs-react": "^1.2.7", ++ "@backstage/plugin-user-settings": "^0.8.11", ++ "@backstage/theme": "^0.5.6", + "@material-ui/core": "^4.12.2", +@@ -28,31 +43,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": "^18.0.2", ++ "react-dom": "^18.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", +- "@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", ++ "@backstage/test-utils": "^1.5.10", ++ "@playwright/test": "^1.32.3", ++ "@testing-library/jest-dom": "^6.0.0", ++ "@testing-library/react": "^14.0.0", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^9.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..18da7c4 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -8,5 +8,4 @@ + name="description" +- content="Backstage is an open platform for building developer portals" ++ content="Backstage is an open source framework for building developer portals" + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index b94cac7..ec8ba1d 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { renderWithEffects } from '@backstage/test-utils'; ++import { render, waitFor } from '@testing-library/react'; + import App from './App'; +@@ -22,4 +22,7 @@ describe('App', () => { + +- const rendered = await renderWithEffects(); +- expect(rendered.baseElement).toBeInTheDocument(); ++ const rendered = render(); ++ ++ await waitFor(() => { ++ expect(rendered.baseElement).toBeInTheDocument(); ++ }); + }); +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 8a53583..e24c468 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,6 +13,5 @@ import { + import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; ++import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +-import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -21,2 +20,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'; +@@ -27,5 +28,12 @@ import { Root } from './components/Root'; + +-import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; ++import { ++ AlertDisplay, ++ OAuthRequestDialog, ++ SignInPage, ++} 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 +45,6 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); + bind(apiDocsPlugin.externalRoutes, { +- createComponent: scaffolderPlugin.routes.root, ++ registerApi: catalogImportPlugin.routes.importPage, + }); +@@ -43,3 +52,10 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); ++ bind(orgPlugin.externalRoutes, { ++ catalogIndex: catalogPlugin.routes.catalogIndex, ++ }); ++ }, ++ components: { ++ SignInPage: props => , + }, +@@ -47,8 +63,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -60,5 +73,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -70,6 +85,9 @@ const routes = ( + } ++ path="/catalog-import" ++ element={ ++ ++ ++ ++ } + /> +- } /> + }> +@@ -78,2 +96,3 @@ const routes = ( + } /> ++ } /> + +@@ -81,4 +100,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -88,5 +107,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..853d175 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,22 +1,5 @@ +-/* +- * 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'; + import ExtensionIcon from '@material-ui/icons/Extension'; +-import MapIcon from '@material-ui/icons/MyLocation'; + import LibraryBooks from '@material-ui/icons/LibraryBooks'; +@@ -25,18 +8,23 @@ 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'; ++import { MyGroupsSidebarItem } from '@backstage/plugin-org'; ++import GroupIcon from '@material-ui/icons/People'; + +@@ -59,3 +47,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -63,8 +51,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -79,19 +62,32 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ } to="/search"> + +- ++ + +- {/* Global nav, not org-specific */} +- +- +- +- +- {/* End global nav */} +- +- +- +- ++ }> ++ {/* Global nav, not org-specific */} ++ ++ ++ ++ ++ ++ {/* End global nav */} ++ ++ ++ {/* Items in this group will be scrollable if they run out of space */} ++ ++ + + +- ++ } ++ 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..6164c76 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,7 +27,5 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +-import { +- isGithubActionsAvailable, +- EntityGithubActionsContent, +-} from '@backstage/plugin-github-actions'; + import { +@@ -56,2 +38,32 @@ 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'; ++ ++import { ++ EntityKubernetesContent, ++ isKubernetesAvailable, ++} from '@backstage/plugin-kubernetes'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); + +@@ -61,5 +73,9 @@ const cicdContent = ( + +- +- +- ++ {/* ++ Here you can add support for different CI/CD services, for example ++ using @backstage-community/plugin-github-actions as follows: ++ ++ ++ ++ */} + +@@ -94,2 +110,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -110,2 +134,6 @@ const overviewContent = ( + ++ ++ ++ ++ + +@@ -129,2 +157,10 @@ const serviceEntityPage = ( + ++ ++ ++ ++ + +@@ -152,3 +188,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -167,2 +203,10 @@ const websiteEntityPage = ( + ++ ++ ++ ++ + +@@ -179,3 +223,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -198,3 +242,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -225,2 +269,5 @@ const apiPage = ( + ++ ++ ++ + +@@ -276,5 +323,8 @@ const groupPage = ( + +- ++ + + ++ ++ ++ + +@@ -292,3 +342,9 @@ const systemPage = ( + +- ++ ++ ++ ++ ++ ++ ++ + +@@ -304,3 +360,19 @@ const systemPage = ( + +- ++ + +@@ -317,2 +389,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/app/src/index.tsx b/packages/app/src/index.tsx +index b16aaf7..d875c77 100644 +--- a/packages/app/src/index.tsx ++++ b/packages/app/src/index.tsx +@@ -2,5 +2,5 @@ import '@backstage/cli/asset-types'; + import React from 'react'; +-import ReactDOM from 'react-dom'; ++import ReactDOM from 'react-dom/client'; + import App from './App'; + +-ReactDOM.render(, document.getElementById('root')); ++ReactDOM.createRoot(document.getElementById('root')!).render(); +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..bef67b5 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,15 @@ 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 ++ ++# This will include the examples, if you don't need these simply remove this line ++COPY --chown=node:node examples ./examples + + # 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..6a14abc 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,33 +6,43 @@ + "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", +- "dockerode": "^3.3.1", +- "express": "^4.17.1", +- "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "@backstage/backend-common": "^0.24.0", ++ "@backstage/backend-defaults": "^0.4.2", ++ "@backstage/backend-tasks": "^0.6.0", ++ "@backstage/config": "^1.2.0", ++ "@backstage/plugin-app-backend": "^0.3.72", ++ "@backstage/plugin-auth-backend": "^0.22.10", ++ "@backstage/plugin-auth-backend-module-github-provider": "^0.1.20", ++ "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.9", ++ "@backstage/plugin-auth-node": "^0.5.0", ++ "@backstage/plugin-catalog-backend": "^1.25.0", ++ "@backstage/plugin-catalog-backend-module-logs": "^0.0.2", ++ "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.21", ++ "@backstage/plugin-kubernetes-backend": "^0.18.4", ++ "@backstage/plugin-permission-backend": "^0.5.47", ++ "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.1.20", ++ "@backstage/plugin-permission-common": "^0.8.1", ++ "@backstage/plugin-permission-node": "^0.8.1", ++ "@backstage/plugin-proxy-backend": "^0.5.4", ++ "@backstage/plugin-scaffolder-backend": "^1.24.0", ++ "@backstage/plugin-search-backend": "^1.5.15", ++ "@backstage/plugin-search-backend-module-catalog": "^0.2.0", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.33", ++ "@backstage/plugin-search-backend-module-techdocs": "^0.2.0", ++ "@backstage/plugin-search-backend-node": "^1.3.0", ++ "@backstage/plugin-techdocs-backend": "^1.10.10", ++ "app": "link:../app", ++ "better-sqlite3": "^9.0.0", ++ "node-gyp": "^10.0.0", ++ "pg": "^8.11.3", + "winston": "^3.2.1" +@@ -40,6 +50,6 @@ + "devDependencies": { +- "@backstage/cli": "^0.10.5", +- "@types/dockerode": "^3.3.0", ++ "@backstage/cli": "^0.27.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.test.ts b/packages/backend/src/index.test.ts +deleted file mode 100644 +index 7814b8c..0000000 +--- a/packages/backend/src/index.test.ts ++++ /dev/null +@@ -1,8 +0,0 @@ +-import { PluginEnvironment } from './types'; +- +-describe('test', () => { +- it('unbreaks the test runner', () => { +- const unbreaker = {} as PluginEnvironment; +- expect(unbreaker).toBeTruthy(); +- }); +-}); +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 08d21e6..f57d4c5 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -8,96 +8,47 @@ + +-import Router from 'express-promise-router'; +-import { +- createServiceBuilder, +- loadBackendConfig, +- getRootLogger, +- useHotMemoize, +- notFoundHandler, +- CacheManager, +- DatabaseManager, +- SingleHostDiscovery, +- UrlReaders, +- ServerTokenManager, +-} from '@backstage/backend-common'; +-import { TaskScheduler } from '@backstage/backend-tasks'; +-import { Config } from '@backstage/config'; +-import app from './plugins/app'; +-import auth from './plugins/auth'; +-import catalog from './plugins/catalog'; +-import scaffolder from './plugins/scaffolder'; +-import proxy from './plugins/proxy'; +-import techdocs from './plugins/techdocs'; +-import search from './plugins/search'; +-import { PluginEnvironment } from './types'; ++import { createBackend } from '@backstage/backend-defaults'; + +-function makeCreateEnv(config: Config) { +- const root = getRootLogger(); +- const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); +- const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); ++const backend = createBackend(); + +- root.info(`Created UrlReader ${reader}`); ++backend.add(import('@backstage/plugin-app-backend/alpha')); ++backend.add(import('@backstage/plugin-proxy-backend/alpha')); ++backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); ++backend.add(import('@backstage/plugin-techdocs-backend/alpha')); + +- return (plugin: string): PluginEnvironment => { +- const logger = root.child({ type: 'plugin', plugin }); +- const database = databaseManager.forPlugin(plugin); +- const cache = cacheManager.forPlugin(plugin); +- const scheduler = taskScheduler.forPlugin(plugin); +- return { +- logger, +- database, +- cache, +- config, +- reader, +- discovery, +- tokenManager, +- scheduler, +- }; +- }; +-} ++// auth plugin ++backend.add(import('@backstage/plugin-auth-backend')); ++// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin ++backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); ++// See https://backstage.io/docs/auth/guest/provider + +-async function main() { +- const config = await loadBackendConfig({ +- argv: process.argv, +- logger: getRootLogger(), +- }); +- const createEnv = makeCreateEnv(config); ++// catalog plugin ++backend.add(import('@backstage/plugin-catalog-backend/alpha')); ++backend.add( ++ import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ++); + +- const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); +- const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); +- const authEnv = useHotMemoize(module, () => createEnv('auth')); +- const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); +- const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); +- const searchEnv = useHotMemoize(module, () => createEnv('search')); +- const appEnv = useHotMemoize(module, () => createEnv('app')); ++// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors ++backend.add(import('@backstage/plugin-catalog-backend-module-logs')); + +- const apiRouter = Router(); +- apiRouter.use('/catalog', await catalog(catalogEnv)); +- apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); +- apiRouter.use('/auth', await auth(authEnv)); +- apiRouter.use('/techdocs', await techdocs(techdocsEnv)); +- apiRouter.use('/proxy', await proxy(proxyEnv)); +- apiRouter.use('/search', await search(searchEnv)); ++// permission plugin ++backend.add(import('@backstage/plugin-permission-backend/alpha')); ++// See https://backstage.io/docs/permissions/getting-started for how to create your own permission policy ++backend.add( ++ import('@backstage/plugin-permission-backend-module-allow-all-policy'), ++); + +- // Add backends ABOVE this line; this 404 handler is the catch-all fallback +- apiRouter.use(notFoundHandler()); ++// search plugin ++backend.add(import('@backstage/plugin-search-backend/alpha')); + +- const service = createServiceBuilder(module) +- .loadConfig(config) +- .addRouter('/api', apiRouter) +- .addRouter('', await app(appEnv)); ++// search engine ++// See https://backstage.io/docs/features/search/search-engines ++backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); + +- await service.start().catch(err => { +- console.log(err); +- process.exit(1); +- }); +-} ++// search collators ++backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); ++backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); + +-module.hot?.accept(); +-main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); +- process.exit(1); +-}); ++// kubernetes ++backend.add(import('@backstage/plugin-kubernetes-backend/alpha')); ++ ++backend.start(); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +deleted file mode 100644 +index 07fb04f..0000000 +--- a/packages/backend/src/plugins/app.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { createRouter } from '@backstage/plugin-app-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +-}: PluginEnvironment): Promise { +- return await createRouter({ +- logger, +- config, +- appPackageName: 'app', +- }); +-} +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +deleted file mode 100644 +index 5216510..0000000 +--- a/packages/backend/src/plugins/auth.ts ++++ /dev/null +@@ -1,12 +0,0 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, database, discovery }); +-} +diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts +deleted file mode 100644 +index 876cb6b..0000000 +--- a/packages/backend/src/plugins/catalog.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +-import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const builder = await CatalogBuilder.create(env); +- builder.addProcessor(new ScaffolderEntitiesProcessor()); +- const { processingEngine, router } = await builder.build(); +- await processingEngine.start(); +- return router; +-} +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +deleted file mode 100644 +index 506f6d9..0000000 +--- a/packages/backend/src/plugins/proxy.ts ++++ /dev/null +@@ -1,11 +0,0 @@ +-import { createRouter } from '@backstage/plugin-proxy-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); +-} +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +deleted file mode 100644 +index 6be2e97..0000000 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ /dev/null +@@ -1,27 +0,0 @@ +-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'; +-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 }); +- +- return await createRouter({ +- containerRunner, +- logger, +- config, +- database, +- catalogClient, +- reader, +- }); +-} +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +deleted file mode 100644 +index f23b0c7..0000000 +--- a/packages/backend/src/plugins/search.ts ++++ /dev/null +@@ -1,54 +0,0 @@ +-import { useHotCleanup } from '@backstage/backend-common'; +-import { createRouter } from '@backstage/plugin-search-backend'; +-import { +- IndexBuilder, +- LunrSearchEngine, +-} from '@backstage/plugin-search-backend-node'; +-import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend'; +- +-export default async function createPlugin({ +- logger, +- discovery, +- config, +- tokenManager, +-}: PluginEnvironment) { +- // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); +- +- // Collators are responsible for gathering documents known to plugins. This +- // collator gathers entities from the software catalog. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { +- discovery, +- tokenManager, +- }), +- }); +- +- // collator gathers entities from techdocs. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultTechDocsCollator.fromConfig(config, { +- discovery, +- logger, +- tokenManager, +- }), +- }); +- +- // The scheduler controls when documents are gathered from collators and sent +- // to the search engine for indexing. +- const { scheduler } = await indexBuilder.build(); +- +- // 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()); +- +- return await createRouter({ +- engine: indexBuilder.getSearchEngine(), +- logger, +- }); +-} +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +deleted file mode 100644 +index 054c64d..0000000 +--- a/packages/backend/src/plugins/techdocs.ts ++++ /dev/null +@@ -1,55 +0,0 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; +-import { +- createRouter, +- Generators, +- Preparers, +- Publisher, +-} from '@backstage/plugin-techdocs-backend'; +-import Docker from 'dockerode'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +- cache, +-}: PluginEnvironment): Promise { +- // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, +- }); +- +- // Docker client (conditionally) used by the generators, based on techdocs.generators config. +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- +- // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, +- containerRunner, +- }); +- +- // Publisher is used for +- // 1. Publishing generated files to storage +- // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, +- }); +- +- // checks if the publisher is working and logs the result +- await publisher.getReadiness(); +- +- return await createRouter({ +- preparers, +- generators, +- publisher, +- logger, +- config, +- discovery, +- cache, +- }); +-} +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +deleted file mode 100644 +index c3d0158..0000000 +--- a/packages/backend/src/types.ts ++++ /dev/null +@@ -1,21 +0,0 @@ +-import { Logger } from 'winston'; +-import { Config } from '@backstage/config'; +-import { +- PluginCacheManager, +- PluginDatabaseManager, +- PluginEndpointDiscovery, +- TokenManager, +- UrlReader, +-} from '@backstage/backend-common'; +-import { PluginTaskScheduler } from '@backstage/backend-tasks'; +- +-export type PluginEnvironment = { +- logger: Logger; +- database: PluginDatabaseManager; +- cache: PluginCacheManager; +- config: Config; +- reader: UrlReader; +- discovery: PluginEndpointDiscovery; +- tokenManager: TokenManager; +- scheduler: PluginTaskScheduler; +-}; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..733be13 +--- /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 dev', ++ 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..c37fc61 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1,3 @@ ++# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. ++# yarn lockfile v1 ++ diff --git a/diffs/0.4.12..0.5.18.diff b/diffs/0.4.12..0.5.18.diff new file mode 100644 index 00000000..de322e1b --- /dev/null +++ b/diffs/0.4.12..0.5.18.diff @@ -0,0 +1,2076 @@ +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..5d426f5 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,51 @@ 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/apis/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 ++ ++auth: ++ providers: ++ guest: {} ++ ++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: ++ # Local example data, replace this with your production config, these are intended for demo use only. ++ # File locations are relative to the backend process, typically in a deployed context, such as in a Docker container, this will be the root ++ - type: file ++ target: ./examples/entities.yaml ++ ++ # Local example template ++ - type: file ++ target: ./examples/template/template.yaml ++ rules: ++ - allow: [Template] ++ ++ # Local example organizational data ++ - type: file ++ target: ./examples/org.yaml ++ rules: ++ - allow: [User, Group] +diff --git a/app-config.yaml b/app-config.yaml +index 1a622a2..ca52ec5 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/integrations/github/locations#configuration + 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 + +@@ -58,3 +66,5 @@ auth: + # see https://backstage.io/docs/auth/ to learn about auth providers +- providers: {} ++ providers: ++ # See https://backstage.io/docs/auth/guest/provider ++ guest: {} + +@@ -64,41 +74,40 @@ 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 ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml ++ rules: ++ - allow: [Template] + +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml ++ rules: ++ - allow: [User, Group] + +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml + +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.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] + +- # 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 +- 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 +- rules: +- - allow: [Template] ++kubernetes: ++ # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options ++ ++# see https://backstage.io/docs/permissions/getting-started for more on the permission framework ++permission: ++ # setting this to `false` will disable permissions ++ enabled: true +diff --git a/backstage.json b/backstage.json +index d3585dd..230d39d 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "0.4.12" ++ "version": "1.30.0" + } +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..80a963e 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.27.0", ++ "@backstage/e2e-test-utils": "^0.1.1", ++ "@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": "^10.0.0", ++ "prettier": "^2.3.2", ++ "typescript": "~5.4.0" ++ }, ++ "resolutions": { ++ "@types/react": "^18", ++ "@types/react-dom": "^18" + }, +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..839ff88 +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,27 @@ ++/* ++ * 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('/'); ++ ++ const enterButton = page.getByRole('button', { name: 'Enter' }); ++ await expect(enterButton).toBeVisible(); ++ await enterButton.click(); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 6fbdef4..346ffc1 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,22 +5,37 @@ + "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.5.10", ++ "@backstage/catalog-model": "^1.6.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/core-app-api": "^1.14.2", ++ "@backstage/core-components": "^0.14.10", ++ "@backstage/core-plugin-api": "^1.9.3", ++ "@backstage/integration-react": "^1.1.30", ++ "@backstage/plugin-api-docs": "^0.11.8", ++ "@backstage/plugin-catalog": "^1.22.0", ++ "@backstage/plugin-catalog-common": "^1.0.26", ++ "@backstage/plugin-catalog-graph": "^0.4.8", ++ "@backstage/plugin-catalog-import": "^0.12.2", ++ "@backstage/plugin-catalog-react": "^1.12.3", ++ "@backstage/plugin-kubernetes": "^0.11.13", ++ "@backstage/plugin-org": "^0.6.28", ++ "@backstage/plugin-permission-react": "^0.4.25", ++ "@backstage/plugin-scaffolder": "^1.24.0", ++ "@backstage/plugin-search": "^1.4.15", ++ "@backstage/plugin-search-react": "^1.7.14", ++ "@backstage/plugin-techdocs": "^1.10.8", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.13", ++ "@backstage/plugin-techdocs-react": "^1.2.7", ++ "@backstage/plugin-user-settings": "^0.8.11", ++ "@backstage/theme": "^0.5.6", + "@material-ui/core": "^4.12.2", +@@ -28,31 +43,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": "^18.0.2", ++ "react-dom": "^18.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", +- "@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", ++ "@backstage/test-utils": "^1.5.10", ++ "@playwright/test": "^1.32.3", ++ "@testing-library/jest-dom": "^6.0.0", ++ "@testing-library/react": "^14.0.0", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^9.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..18da7c4 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -8,5 +8,4 @@ + name="description" +- content="Backstage is an open platform for building developer portals" ++ content="Backstage is an open source framework for building developer portals" + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index b94cac7..ec8ba1d 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { renderWithEffects } from '@backstage/test-utils'; ++import { render, waitFor } from '@testing-library/react'; + import App from './App'; +@@ -22,4 +22,7 @@ describe('App', () => { + +- const rendered = await renderWithEffects(); +- expect(rendered.baseElement).toBeInTheDocument(); ++ const rendered = render(); ++ ++ await waitFor(() => { ++ expect(rendered.baseElement).toBeInTheDocument(); ++ }); + }); +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 8a53583..e24c468 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,6 +13,5 @@ import { + import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; ++import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +-import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -21,2 +20,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'; +@@ -27,5 +28,12 @@ import { Root } from './components/Root'; + +-import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; ++import { ++ AlertDisplay, ++ OAuthRequestDialog, ++ SignInPage, ++} 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 +45,6 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); + bind(apiDocsPlugin.externalRoutes, { +- createComponent: scaffolderPlugin.routes.root, ++ registerApi: catalogImportPlugin.routes.importPage, + }); +@@ -43,3 +52,10 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); ++ bind(orgPlugin.externalRoutes, { ++ catalogIndex: catalogPlugin.routes.catalogIndex, ++ }); ++ }, ++ components: { ++ SignInPage: props => , + }, +@@ -47,8 +63,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -60,5 +73,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -70,6 +85,9 @@ const routes = ( + } ++ path="/catalog-import" ++ element={ ++ ++ ++ ++ } + /> +- } /> + }> +@@ -78,2 +96,3 @@ const routes = ( + } /> ++ } /> + +@@ -81,4 +100,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -88,5 +107,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..853d175 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,22 +1,5 @@ +-/* +- * 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'; + import ExtensionIcon from '@material-ui/icons/Extension'; +-import MapIcon from '@material-ui/icons/MyLocation'; + import LibraryBooks from '@material-ui/icons/LibraryBooks'; +@@ -25,18 +8,23 @@ 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'; ++import { MyGroupsSidebarItem } from '@backstage/plugin-org'; ++import GroupIcon from '@material-ui/icons/People'; + +@@ -59,3 +47,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -63,8 +51,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -79,19 +62,32 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ } to="/search"> + +- ++ + +- {/* Global nav, not org-specific */} +- +- +- +- +- {/* End global nav */} +- +- +- +- ++ }> ++ {/* Global nav, not org-specific */} ++ ++ ++ ++ ++ ++ {/* End global nav */} ++ ++ ++ {/* Items in this group will be scrollable if they run out of space */} ++ ++ + + +- ++ } ++ 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..6164c76 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,7 +27,5 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +-import { +- isGithubActionsAvailable, +- EntityGithubActionsContent, +-} from '@backstage/plugin-github-actions'; + import { +@@ -56,2 +38,32 @@ 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'; ++ ++import { ++ EntityKubernetesContent, ++ isKubernetesAvailable, ++} from '@backstage/plugin-kubernetes'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); + +@@ -61,5 +73,9 @@ const cicdContent = ( + +- +- +- ++ {/* ++ Here you can add support for different CI/CD services, for example ++ using @backstage-community/plugin-github-actions as follows: ++ ++ ++ ++ */} + +@@ -94,2 +110,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -110,2 +134,6 @@ const overviewContent = ( + ++ ++ ++ ++ + +@@ -129,2 +157,10 @@ const serviceEntityPage = ( + ++ ++ ++ ++ + +@@ -152,3 +188,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -167,2 +203,10 @@ const websiteEntityPage = ( + ++ ++ ++ ++ + +@@ -179,3 +223,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -198,3 +242,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -225,2 +269,5 @@ const apiPage = ( + ++ ++ ++ + +@@ -276,5 +323,8 @@ const groupPage = ( + +- ++ + + ++ ++ ++ + +@@ -292,3 +342,9 @@ const systemPage = ( + +- ++ ++ ++ ++ ++ ++ ++ + +@@ -304,3 +360,19 @@ const systemPage = ( + +- ++ + +@@ -317,2 +389,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/app/src/index.tsx b/packages/app/src/index.tsx +index b16aaf7..d875c77 100644 +--- a/packages/app/src/index.tsx ++++ b/packages/app/src/index.tsx +@@ -2,5 +2,5 @@ import '@backstage/cli/asset-types'; + import React from 'react'; +-import ReactDOM from 'react-dom'; ++import ReactDOM from 'react-dom/client'; + import App from './App'; + +-ReactDOM.render(, document.getElementById('root')); ++ReactDOM.createRoot(document.getElementById('root')!).render(); +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..bef67b5 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,15 @@ 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 ++ ++# This will include the examples, if you don't need these simply remove this line ++COPY --chown=node:node examples ./examples + + # 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..6a14abc 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,35 +6,43 @@ + "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", +- "dockerode": "^3.3.1", +- "express": "^4.17.1", +- "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "@backstage/backend-common": "^0.24.0", ++ "@backstage/backend-defaults": "^0.4.2", ++ "@backstage/backend-tasks": "^0.6.0", ++ "@backstage/config": "^1.2.0", ++ "@backstage/plugin-app-backend": "^0.3.72", ++ "@backstage/plugin-auth-backend": "^0.22.10", ++ "@backstage/plugin-auth-backend-module-github-provider": "^0.1.20", ++ "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.9", ++ "@backstage/plugin-auth-node": "^0.5.0", ++ "@backstage/plugin-catalog-backend": "^1.25.0", ++ "@backstage/plugin-catalog-backend-module-logs": "^0.0.2", ++ "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.21", ++ "@backstage/plugin-kubernetes-backend": "^0.18.4", ++ "@backstage/plugin-permission-backend": "^0.5.47", ++ "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.1.20", ++ "@backstage/plugin-permission-common": "^0.8.1", ++ "@backstage/plugin-permission-node": "^0.8.1", ++ "@backstage/plugin-proxy-backend": "^0.5.4", ++ "@backstage/plugin-scaffolder-backend": "^1.24.0", ++ "@backstage/plugin-search-backend": "^1.5.15", ++ "@backstage/plugin-search-backend-module-catalog": "^0.2.0", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.33", ++ "@backstage/plugin-search-backend-module-techdocs": "^0.2.0", ++ "@backstage/plugin-search-backend-node": "^1.3.0", ++ "@backstage/plugin-techdocs-backend": "^1.10.10", ++ "app": "link:../app", ++ "better-sqlite3": "^9.0.0", ++ "node-gyp": "^10.0.0", ++ "pg": "^8.11.3", + "winston": "^3.2.1" +@@ -42,6 +50,6 @@ + "devDependencies": { +- "@backstage/cli": "^0.11.0", +- "@types/dockerode": "^3.3.0", ++ "@backstage/cli": "^0.27.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.test.ts b/packages/backend/src/index.test.ts +deleted file mode 100644 +index 7814b8c..0000000 +--- a/packages/backend/src/index.test.ts ++++ /dev/null +@@ -1,8 +0,0 @@ +-import { PluginEnvironment } from './types'; +- +-describe('test', () => { +- it('unbreaks the test runner', () => { +- const unbreaker = {} as PluginEnvironment; +- expect(unbreaker).toBeTruthy(); +- }); +-}); +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 70bc66b..f57d4c5 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -8,102 +8,47 @@ + +-import Router from 'express-promise-router'; +-import { +- createServiceBuilder, +- loadBackendConfig, +- getRootLogger, +- useHotMemoize, +- notFoundHandler, +- CacheManager, +- DatabaseManager, +- SingleHostDiscovery, +- UrlReaders, +- ServerTokenManager, +-} from '@backstage/backend-common'; +-import { TaskScheduler } from '@backstage/backend-tasks'; +-import { Config } from '@backstage/config'; +-import app from './plugins/app'; +-import auth from './plugins/auth'; +-import catalog from './plugins/catalog'; +-import scaffolder from './plugins/scaffolder'; +-import proxy from './plugins/proxy'; +-import techdocs from './plugins/techdocs'; +-import search from './plugins/search'; +-import { PluginEnvironment } from './types'; +-import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { createBackend } from '@backstage/backend-defaults'; + +-function makeCreateEnv(config: Config) { +- const root = getRootLogger(); +- const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); +- const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); +- const permissions = ServerPermissionClient.fromConfig(config, { +- discovery, +- tokenManager, +- }); ++const backend = createBackend(); + +- root.info(`Created UrlReader ${reader}`); ++backend.add(import('@backstage/plugin-app-backend/alpha')); ++backend.add(import('@backstage/plugin-proxy-backend/alpha')); ++backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); ++backend.add(import('@backstage/plugin-techdocs-backend/alpha')); + +- return (plugin: string): PluginEnvironment => { +- const logger = root.child({ type: 'plugin', plugin }); +- const database = databaseManager.forPlugin(plugin); +- const cache = cacheManager.forPlugin(plugin); +- const scheduler = taskScheduler.forPlugin(plugin); +- return { +- logger, +- database, +- cache, +- config, +- reader, +- discovery, +- tokenManager, +- scheduler, +- permissions, +- }; +- }; +-} ++// auth plugin ++backend.add(import('@backstage/plugin-auth-backend')); ++// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin ++backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); ++// See https://backstage.io/docs/auth/guest/provider + +-async function main() { +- const config = await loadBackendConfig({ +- argv: process.argv, +- logger: getRootLogger(), +- }); +- const createEnv = makeCreateEnv(config); ++// catalog plugin ++backend.add(import('@backstage/plugin-catalog-backend/alpha')); ++backend.add( ++ import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ++); + +- const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); +- const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); +- const authEnv = useHotMemoize(module, () => createEnv('auth')); +- const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); +- const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); +- const searchEnv = useHotMemoize(module, () => createEnv('search')); +- const appEnv = useHotMemoize(module, () => createEnv('app')); ++// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors ++backend.add(import('@backstage/plugin-catalog-backend-module-logs')); + +- const apiRouter = Router(); +- apiRouter.use('/catalog', await catalog(catalogEnv)); +- apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); +- apiRouter.use('/auth', await auth(authEnv)); +- apiRouter.use('/techdocs', await techdocs(techdocsEnv)); +- apiRouter.use('/proxy', await proxy(proxyEnv)); +- apiRouter.use('/search', await search(searchEnv)); ++// permission plugin ++backend.add(import('@backstage/plugin-permission-backend/alpha')); ++// See https://backstage.io/docs/permissions/getting-started for how to create your own permission policy ++backend.add( ++ import('@backstage/plugin-permission-backend-module-allow-all-policy'), ++); + +- // Add backends ABOVE this line; this 404 handler is the catch-all fallback +- apiRouter.use(notFoundHandler()); ++// search plugin ++backend.add(import('@backstage/plugin-search-backend/alpha')); + +- const service = createServiceBuilder(module) +- .loadConfig(config) +- .addRouter('/api', apiRouter) +- .addRouter('', await app(appEnv)); ++// search engine ++// See https://backstage.io/docs/features/search/search-engines ++backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); + +- await service.start().catch(err => { +- console.log(err); +- process.exit(1); +- }); +-} ++// search collators ++backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); ++backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); + +-module.hot?.accept(); +-main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); +- process.exit(1); +-}); ++// kubernetes ++backend.add(import('@backstage/plugin-kubernetes-backend/alpha')); ++ ++backend.start(); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +deleted file mode 100644 +index 07fb04f..0000000 +--- a/packages/backend/src/plugins/app.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { createRouter } from '@backstage/plugin-app-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +-}: PluginEnvironment): Promise { +- return await createRouter({ +- logger, +- config, +- appPackageName: 'app', +- }); +-} +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +deleted file mode 100644 +index 5216510..0000000 +--- a/packages/backend/src/plugins/auth.ts ++++ /dev/null +@@ -1,12 +0,0 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, database, discovery }); +-} +diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts +deleted file mode 100644 +index 876cb6b..0000000 +--- a/packages/backend/src/plugins/catalog.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +-import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const builder = await CatalogBuilder.create(env); +- builder.addProcessor(new ScaffolderEntitiesProcessor()); +- const { processingEngine, router } = await builder.build(); +- await processingEngine.start(); +- return router; +-} +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +deleted file mode 100644 +index 506f6d9..0000000 +--- a/packages/backend/src/plugins/proxy.ts ++++ /dev/null +@@ -1,11 +0,0 @@ +-import { createRouter } from '@backstage/plugin-proxy-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); +-} +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +deleted file mode 100644 +index 6be2e97..0000000 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ /dev/null +@@ -1,27 +0,0 @@ +-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'; +-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 }); +- +- return await createRouter({ +- containerRunner, +- logger, +- config, +- database, +- catalogClient, +- reader, +- }); +-} +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +deleted file mode 100644 +index f23b0c7..0000000 +--- a/packages/backend/src/plugins/search.ts ++++ /dev/null +@@ -1,54 +0,0 @@ +-import { useHotCleanup } from '@backstage/backend-common'; +-import { createRouter } from '@backstage/plugin-search-backend'; +-import { +- IndexBuilder, +- LunrSearchEngine, +-} from '@backstage/plugin-search-backend-node'; +-import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend'; +- +-export default async function createPlugin({ +- logger, +- discovery, +- config, +- tokenManager, +-}: PluginEnvironment) { +- // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); +- +- // Collators are responsible for gathering documents known to plugins. This +- // collator gathers entities from the software catalog. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { +- discovery, +- tokenManager, +- }), +- }); +- +- // collator gathers entities from techdocs. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultTechDocsCollator.fromConfig(config, { +- discovery, +- logger, +- tokenManager, +- }), +- }); +- +- // The scheduler controls when documents are gathered from collators and sent +- // to the search engine for indexing. +- const { scheduler } = await indexBuilder.build(); +- +- // 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()); +- +- return await createRouter({ +- engine: indexBuilder.getSearchEngine(), +- logger, +- }); +-} +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +deleted file mode 100644 +index 054c64d..0000000 +--- a/packages/backend/src/plugins/techdocs.ts ++++ /dev/null +@@ -1,55 +0,0 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; +-import { +- createRouter, +- Generators, +- Preparers, +- Publisher, +-} from '@backstage/plugin-techdocs-backend'; +-import Docker from 'dockerode'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +- cache, +-}: PluginEnvironment): Promise { +- // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, +- }); +- +- // Docker client (conditionally) used by the generators, based on techdocs.generators config. +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- +- // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, +- containerRunner, +- }); +- +- // Publisher is used for +- // 1. Publishing generated files to storage +- // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, +- }); +- +- // checks if the publisher is working and logs the result +- await publisher.getReadiness(); +- +- return await createRouter({ +- preparers, +- generators, +- publisher, +- logger, +- config, +- discovery, +- cache, +- }); +-} +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +deleted file mode 100644 +index 0862b0e..0000000 +--- a/packages/backend/src/types.ts ++++ /dev/null +@@ -1,23 +0,0 @@ +-import { Logger } from 'winston'; +-import { Config } from '@backstage/config'; +-import { +- PluginCacheManager, +- PluginDatabaseManager, +- PluginEndpointDiscovery, +- TokenManager, +- UrlReader, +-} from '@backstage/backend-common'; +-import { PluginTaskScheduler } from '@backstage/backend-tasks'; +-import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; +- +-export type PluginEnvironment = { +- logger: Logger; +- database: PluginDatabaseManager; +- cache: PluginCacheManager; +- config: Config; +- reader: UrlReader; +- discovery: PluginEndpointDiscovery; +- tokenManager: TokenManager; +- scheduler: PluginTaskScheduler; +- permissions: PermissionAuthorizer; +-}; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..733be13 +--- /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 dev', ++ 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..c37fc61 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1,3 @@ ++# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. ++# yarn lockfile v1 ++ diff --git a/diffs/0.4.13..0.5.18.diff b/diffs/0.4.13..0.5.18.diff new file mode 100644 index 00000000..368465eb --- /dev/null +++ b/diffs/0.4.13..0.5.18.diff @@ -0,0 +1,2045 @@ +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..5d426f5 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,51 @@ 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/apis/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 ++ ++auth: ++ providers: ++ guest: {} ++ ++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: ++ # Local example data, replace this with your production config, these are intended for demo use only. ++ # File locations are relative to the backend process, typically in a deployed context, such as in a Docker container, this will be the root ++ - type: file ++ target: ./examples/entities.yaml ++ ++ # Local example template ++ - type: file ++ target: ./examples/template/template.yaml ++ rules: ++ - allow: [Template] ++ ++ # Local example organizational data ++ - type: file ++ target: ./examples/org.yaml ++ rules: ++ - allow: [User, Group] +diff --git a/app-config.yaml b/app-config.yaml +index 1a622a2..ca52ec5 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/integrations/github/locations#configuration + 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 + +@@ -58,3 +66,5 @@ auth: + # see https://backstage.io/docs/auth/ to learn about auth providers +- providers: {} ++ providers: ++ # See https://backstage.io/docs/auth/guest/provider ++ guest: {} + +@@ -64,41 +74,40 @@ 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 ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml ++ rules: ++ - allow: [Template] + +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml ++ rules: ++ - allow: [User, Group] + +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml + +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.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] + +- # 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 +- 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 +- rules: +- - allow: [Template] ++kubernetes: ++ # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options ++ ++# see https://backstage.io/docs/permissions/getting-started for more on the permission framework ++permission: ++ # setting this to `false` will disable permissions ++ enabled: true +diff --git a/backstage.json b/backstage.json +index 5a69ad5..230d39d 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "0.4.13" ++ "version": "1.30.0" + } +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..80a963e 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.27.0", ++ "@backstage/e2e-test-utils": "^0.1.1", ++ "@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": "^10.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.5.4" ++ "typescript": "~5.4.0" ++ }, ++ "resolutions": { ++ "@types/react": "^18", ++ "@types/react-dom": "^18" + }, +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..839ff88 +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,27 @@ ++/* ++ * 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('/'); ++ ++ const enterButton = page.getByRole('button', { name: 'Enter' }); ++ await expect(enterButton).toBeVisible(); ++ await enterButton.click(); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 72c01d5..346ffc1 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,22 +5,37 @@ + "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.5.10", ++ "@backstage/catalog-model": "^1.6.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/core-app-api": "^1.14.2", ++ "@backstage/core-components": "^0.14.10", ++ "@backstage/core-plugin-api": "^1.9.3", ++ "@backstage/integration-react": "^1.1.30", ++ "@backstage/plugin-api-docs": "^0.11.8", ++ "@backstage/plugin-catalog": "^1.22.0", ++ "@backstage/plugin-catalog-common": "^1.0.26", ++ "@backstage/plugin-catalog-graph": "^0.4.8", ++ "@backstage/plugin-catalog-import": "^0.12.2", ++ "@backstage/plugin-catalog-react": "^1.12.3", ++ "@backstage/plugin-kubernetes": "^0.11.13", ++ "@backstage/plugin-org": "^0.6.28", ++ "@backstage/plugin-permission-react": "^0.4.25", ++ "@backstage/plugin-scaffolder": "^1.24.0", ++ "@backstage/plugin-search": "^1.4.15", ++ "@backstage/plugin-search-react": "^1.7.14", ++ "@backstage/plugin-techdocs": "^1.10.8", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.13", ++ "@backstage/plugin-techdocs-react": "^1.2.7", ++ "@backstage/plugin-user-settings": "^0.8.11", ++ "@backstage/theme": "^0.5.6", + "@material-ui/core": "^4.12.2", +@@ -28,31 +43,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": "^18.0.2", ++ "react-dom": "^18.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", +- "@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", ++ "@backstage/test-utils": "^1.5.10", ++ "@playwright/test": "^1.32.3", ++ "@testing-library/jest-dom": "^6.0.0", ++ "@testing-library/react": "^14.0.0", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^9.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..18da7c4 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -8,5 +8,4 @@ + name="description" +- content="Backstage is an open platform for building developer portals" ++ content="Backstage is an open source framework for building developer portals" + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index b94cac7..ec8ba1d 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { renderWithEffects } from '@backstage/test-utils'; ++import { render, waitFor } from '@testing-library/react'; + import App from './App'; +@@ -22,4 +22,7 @@ describe('App', () => { + +- const rendered = await renderWithEffects(); +- expect(rendered.baseElement).toBeInTheDocument(); ++ const rendered = render(); ++ ++ await waitFor(() => { ++ expect(rendered.baseElement).toBeInTheDocument(); ++ }); + }); +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 8a53583..e24c468 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,6 +13,5 @@ import { + import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; ++import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +-import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -21,2 +20,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'; +@@ -27,5 +28,12 @@ import { Root } from './components/Root'; + +-import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; ++import { ++ AlertDisplay, ++ OAuthRequestDialog, ++ SignInPage, ++} 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 +45,6 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); + bind(apiDocsPlugin.externalRoutes, { +- createComponent: scaffolderPlugin.routes.root, ++ registerApi: catalogImportPlugin.routes.importPage, + }); +@@ -43,3 +52,10 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); ++ bind(orgPlugin.externalRoutes, { ++ catalogIndex: catalogPlugin.routes.catalogIndex, ++ }); ++ }, ++ components: { ++ SignInPage: props => , + }, +@@ -47,8 +63,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -60,5 +73,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -70,6 +85,9 @@ const routes = ( + } ++ path="/catalog-import" ++ element={ ++ ++ ++ ++ } + /> +- } /> + }> +@@ -78,2 +96,3 @@ const routes = ( + } /> ++ } /> + +@@ -81,4 +100,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -88,5 +107,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..853d175 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,22 +1,5 @@ +-/* +- * 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'; + import ExtensionIcon from '@material-ui/icons/Extension'; +-import MapIcon from '@material-ui/icons/MyLocation'; + import LibraryBooks from '@material-ui/icons/LibraryBooks'; +@@ -25,3 +8,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -30,6 +12,3 @@ import { + } from '@backstage/plugin-user-settings'; +-import { +- SidebarSearchModal, +- SearchContextProvider, +-} from '@backstage/plugin-search'; ++import { SidebarSearchModal } from '@backstage/plugin-search'; + import { +@@ -37,3 +16,2 @@ import { + sidebarConfig, +- SidebarContext, + SidebarDivider, +@@ -44,2 +22,4 @@ import { + SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -47,2 +27,4 @@ import MenuIcon from '@material-ui/icons/Menu'; + import SearchIcon from '@material-ui/icons/Search'; ++import { MyGroupsSidebarItem } from '@backstage/plugin-org'; ++import GroupIcon from '@material-ui/icons/People'; + +@@ -65,3 +47,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -69,8 +51,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -86,5 +63,3 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + } to="/search"> +- +- +- {' '} ++ + +@@ -94,2 +69,7 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + ++ + +@@ -100,3 +80,3 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ {/* Items in this group will be scrollable if they run out of space */} + +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..6164c76 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,7 +27,5 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +-import { +- isGithubActionsAvailable, +- EntityGithubActionsContent, +-} from '@backstage/plugin-github-actions'; + import { +@@ -56,2 +38,32 @@ 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'; ++ ++import { ++ EntityKubernetesContent, ++ isKubernetesAvailable, ++} from '@backstage/plugin-kubernetes'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); + +@@ -61,5 +73,9 @@ const cicdContent = ( + +- +- +- ++ {/* ++ Here you can add support for different CI/CD services, for example ++ using @backstage-community/plugin-github-actions as follows: ++ ++ ++ ++ */} + +@@ -94,2 +110,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -110,2 +134,6 @@ const overviewContent = ( + ++ ++ ++ ++ + +@@ -129,2 +157,10 @@ const serviceEntityPage = ( + ++ ++ ++ ++ + +@@ -152,3 +188,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -167,2 +203,10 @@ const websiteEntityPage = ( + ++ ++ ++ ++ + +@@ -179,3 +223,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -198,3 +242,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -225,2 +269,5 @@ const apiPage = ( + ++ ++ ++ + +@@ -276,5 +323,8 @@ const groupPage = ( + +- ++ + + ++ ++ ++ + +@@ -292,3 +342,9 @@ const systemPage = ( + +- ++ ++ ++ ++ ++ ++ ++ + +@@ -304,3 +360,19 @@ const systemPage = ( + +- ++ + +@@ -317,2 +389,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/app/src/index.tsx b/packages/app/src/index.tsx +index b16aaf7..d875c77 100644 +--- a/packages/app/src/index.tsx ++++ b/packages/app/src/index.tsx +@@ -2,5 +2,5 @@ import '@backstage/cli/asset-types'; + import React from 'react'; +-import ReactDOM from 'react-dom'; ++import ReactDOM from 'react-dom/client'; + import App from './App'; + +-ReactDOM.render(, document.getElementById('root')); ++ReactDOM.createRoot(document.getElementById('root')!).render(); +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..bef67b5 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,15 @@ 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 ++ ++# This will include the examples, if you don't need these simply remove this line ++COPY --chown=node:node examples ./examples + + # 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..6a14abc 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,35 +6,43 @@ + "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", +- "dockerode": "^3.3.1", +- "express": "^4.17.1", +- "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "@backstage/backend-common": "^0.24.0", ++ "@backstage/backend-defaults": "^0.4.2", ++ "@backstage/backend-tasks": "^0.6.0", ++ "@backstage/config": "^1.2.0", ++ "@backstage/plugin-app-backend": "^0.3.72", ++ "@backstage/plugin-auth-backend": "^0.22.10", ++ "@backstage/plugin-auth-backend-module-github-provider": "^0.1.20", ++ "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.9", ++ "@backstage/plugin-auth-node": "^0.5.0", ++ "@backstage/plugin-catalog-backend": "^1.25.0", ++ "@backstage/plugin-catalog-backend-module-logs": "^0.0.2", ++ "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.21", ++ "@backstage/plugin-kubernetes-backend": "^0.18.4", ++ "@backstage/plugin-permission-backend": "^0.5.47", ++ "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.1.20", ++ "@backstage/plugin-permission-common": "^0.8.1", ++ "@backstage/plugin-permission-node": "^0.8.1", ++ "@backstage/plugin-proxy-backend": "^0.5.4", ++ "@backstage/plugin-scaffolder-backend": "^1.24.0", ++ "@backstage/plugin-search-backend": "^1.5.15", ++ "@backstage/plugin-search-backend-module-catalog": "^0.2.0", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.33", ++ "@backstage/plugin-search-backend-module-techdocs": "^0.2.0", ++ "@backstage/plugin-search-backend-node": "^1.3.0", ++ "@backstage/plugin-techdocs-backend": "^1.10.10", ++ "app": "link:../app", ++ "better-sqlite3": "^9.0.0", ++ "node-gyp": "^10.0.0", ++ "pg": "^8.11.3", + "winston": "^3.2.1" +@@ -42,6 +50,6 @@ + "devDependencies": { +- "@backstage/cli": "^0.12.0-next.0", +- "@types/dockerode": "^3.3.0", ++ "@backstage/cli": "^0.27.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.test.ts b/packages/backend/src/index.test.ts +deleted file mode 100644 +index 7814b8c..0000000 +--- a/packages/backend/src/index.test.ts ++++ /dev/null +@@ -1,8 +0,0 @@ +-import { PluginEnvironment } from './types'; +- +-describe('test', () => { +- it('unbreaks the test runner', () => { +- const unbreaker = {} as PluginEnvironment; +- expect(unbreaker).toBeTruthy(); +- }); +-}); +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 70bc66b..f57d4c5 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -8,102 +8,47 @@ + +-import Router from 'express-promise-router'; +-import { +- createServiceBuilder, +- loadBackendConfig, +- getRootLogger, +- useHotMemoize, +- notFoundHandler, +- CacheManager, +- DatabaseManager, +- SingleHostDiscovery, +- UrlReaders, +- ServerTokenManager, +-} from '@backstage/backend-common'; +-import { TaskScheduler } from '@backstage/backend-tasks'; +-import { Config } from '@backstage/config'; +-import app from './plugins/app'; +-import auth from './plugins/auth'; +-import catalog from './plugins/catalog'; +-import scaffolder from './plugins/scaffolder'; +-import proxy from './plugins/proxy'; +-import techdocs from './plugins/techdocs'; +-import search from './plugins/search'; +-import { PluginEnvironment } from './types'; +-import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { createBackend } from '@backstage/backend-defaults'; + +-function makeCreateEnv(config: Config) { +- const root = getRootLogger(); +- const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); +- const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); +- const permissions = ServerPermissionClient.fromConfig(config, { +- discovery, +- tokenManager, +- }); ++const backend = createBackend(); + +- root.info(`Created UrlReader ${reader}`); ++backend.add(import('@backstage/plugin-app-backend/alpha')); ++backend.add(import('@backstage/plugin-proxy-backend/alpha')); ++backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); ++backend.add(import('@backstage/plugin-techdocs-backend/alpha')); + +- return (plugin: string): PluginEnvironment => { +- const logger = root.child({ type: 'plugin', plugin }); +- const database = databaseManager.forPlugin(plugin); +- const cache = cacheManager.forPlugin(plugin); +- const scheduler = taskScheduler.forPlugin(plugin); +- return { +- logger, +- database, +- cache, +- config, +- reader, +- discovery, +- tokenManager, +- scheduler, +- permissions, +- }; +- }; +-} ++// auth plugin ++backend.add(import('@backstage/plugin-auth-backend')); ++// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin ++backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); ++// See https://backstage.io/docs/auth/guest/provider + +-async function main() { +- const config = await loadBackendConfig({ +- argv: process.argv, +- logger: getRootLogger(), +- }); +- const createEnv = makeCreateEnv(config); ++// catalog plugin ++backend.add(import('@backstage/plugin-catalog-backend/alpha')); ++backend.add( ++ import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ++); + +- const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); +- const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); +- const authEnv = useHotMemoize(module, () => createEnv('auth')); +- const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); +- const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); +- const searchEnv = useHotMemoize(module, () => createEnv('search')); +- const appEnv = useHotMemoize(module, () => createEnv('app')); ++// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors ++backend.add(import('@backstage/plugin-catalog-backend-module-logs')); + +- const apiRouter = Router(); +- apiRouter.use('/catalog', await catalog(catalogEnv)); +- apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); +- apiRouter.use('/auth', await auth(authEnv)); +- apiRouter.use('/techdocs', await techdocs(techdocsEnv)); +- apiRouter.use('/proxy', await proxy(proxyEnv)); +- apiRouter.use('/search', await search(searchEnv)); ++// permission plugin ++backend.add(import('@backstage/plugin-permission-backend/alpha')); ++// See https://backstage.io/docs/permissions/getting-started for how to create your own permission policy ++backend.add( ++ import('@backstage/plugin-permission-backend-module-allow-all-policy'), ++); + +- // Add backends ABOVE this line; this 404 handler is the catch-all fallback +- apiRouter.use(notFoundHandler()); ++// search plugin ++backend.add(import('@backstage/plugin-search-backend/alpha')); + +- const service = createServiceBuilder(module) +- .loadConfig(config) +- .addRouter('/api', apiRouter) +- .addRouter('', await app(appEnv)); ++// search engine ++// See https://backstage.io/docs/features/search/search-engines ++backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); + +- await service.start().catch(err => { +- console.log(err); +- process.exit(1); +- }); +-} ++// search collators ++backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); ++backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); + +-module.hot?.accept(); +-main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); +- process.exit(1); +-}); ++// kubernetes ++backend.add(import('@backstage/plugin-kubernetes-backend/alpha')); ++ ++backend.start(); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +deleted file mode 100644 +index 14e19a1..0000000 +--- a/packages/backend/src/plugins/app.ts ++++ /dev/null +@@ -1,16 +0,0 @@ +-import { createRouter } from '@backstage/plugin-app-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- database, +-}: PluginEnvironment): Promise { +- return await createRouter({ +- logger, +- config, +- database, +- appPackageName: 'app', +- }); +-} +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +deleted file mode 100644 +index 5216510..0000000 +--- a/packages/backend/src/plugins/auth.ts ++++ /dev/null +@@ -1,12 +0,0 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, database, discovery }); +-} +diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts +deleted file mode 100644 +index 876cb6b..0000000 +--- a/packages/backend/src/plugins/catalog.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +-import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const builder = await CatalogBuilder.create(env); +- builder.addProcessor(new ScaffolderEntitiesProcessor()); +- const { processingEngine, router } = await builder.build(); +- await processingEngine.start(); +- return router; +-} +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +deleted file mode 100644 +index 506f6d9..0000000 +--- a/packages/backend/src/plugins/proxy.ts ++++ /dev/null +@@ -1,11 +0,0 @@ +-import { createRouter } from '@backstage/plugin-proxy-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); +-} +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +deleted file mode 100644 +index 6be2e97..0000000 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ /dev/null +@@ -1,27 +0,0 @@ +-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'; +-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 }); +- +- return await createRouter({ +- containerRunner, +- logger, +- config, +- database, +- catalogClient, +- reader, +- }); +-} +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +deleted file mode 100644 +index f23b0c7..0000000 +--- a/packages/backend/src/plugins/search.ts ++++ /dev/null +@@ -1,54 +0,0 @@ +-import { useHotCleanup } from '@backstage/backend-common'; +-import { createRouter } from '@backstage/plugin-search-backend'; +-import { +- IndexBuilder, +- LunrSearchEngine, +-} from '@backstage/plugin-search-backend-node'; +-import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend'; +- +-export default async function createPlugin({ +- logger, +- discovery, +- config, +- tokenManager, +-}: PluginEnvironment) { +- // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); +- +- // Collators are responsible for gathering documents known to plugins. This +- // collator gathers entities from the software catalog. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { +- discovery, +- tokenManager, +- }), +- }); +- +- // collator gathers entities from techdocs. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultTechDocsCollator.fromConfig(config, { +- discovery, +- logger, +- tokenManager, +- }), +- }); +- +- // The scheduler controls when documents are gathered from collators and sent +- // to the search engine for indexing. +- const { scheduler } = await indexBuilder.build(); +- +- // 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()); +- +- return await createRouter({ +- engine: indexBuilder.getSearchEngine(), +- logger, +- }); +-} +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +deleted file mode 100644 +index 054c64d..0000000 +--- a/packages/backend/src/plugins/techdocs.ts ++++ /dev/null +@@ -1,55 +0,0 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; +-import { +- createRouter, +- Generators, +- Preparers, +- Publisher, +-} from '@backstage/plugin-techdocs-backend'; +-import Docker from 'dockerode'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +- cache, +-}: PluginEnvironment): Promise { +- // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, +- }); +- +- // Docker client (conditionally) used by the generators, based on techdocs.generators config. +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- +- // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, +- containerRunner, +- }); +- +- // Publisher is used for +- // 1. Publishing generated files to storage +- // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, +- }); +- +- // checks if the publisher is working and logs the result +- await publisher.getReadiness(); +- +- return await createRouter({ +- preparers, +- generators, +- publisher, +- logger, +- config, +- discovery, +- cache, +- }); +-} +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +deleted file mode 100644 +index 0862b0e..0000000 +--- a/packages/backend/src/types.ts ++++ /dev/null +@@ -1,23 +0,0 @@ +-import { Logger } from 'winston'; +-import { Config } from '@backstage/config'; +-import { +- PluginCacheManager, +- PluginDatabaseManager, +- PluginEndpointDiscovery, +- TokenManager, +- UrlReader, +-} from '@backstage/backend-common'; +-import { PluginTaskScheduler } from '@backstage/backend-tasks'; +-import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; +- +-export type PluginEnvironment = { +- logger: Logger; +- database: PluginDatabaseManager; +- cache: PluginCacheManager; +- config: Config; +- reader: UrlReader; +- discovery: PluginEndpointDiscovery; +- tokenManager: TokenManager; +- scheduler: PluginTaskScheduler; +- permissions: PermissionAuthorizer; +-}; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..733be13 +--- /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 dev', ++ 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..c37fc61 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1,3 @@ ++# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. ++# yarn lockfile v1 ++ diff --git a/diffs/0.4.14..0.5.18.diff b/diffs/0.4.14..0.5.18.diff new file mode 100644 index 00000000..33bc85df --- /dev/null +++ b/diffs/0.4.14..0.5.18.diff @@ -0,0 +1,2037 @@ +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..5d426f5 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,51 @@ 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/apis/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 ++ ++auth: ++ providers: ++ guest: {} ++ ++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: ++ # Local example data, replace this with your production config, these are intended for demo use only. ++ # File locations are relative to the backend process, typically in a deployed context, such as in a Docker container, this will be the root ++ - type: file ++ target: ./examples/entities.yaml ++ ++ # Local example template ++ - type: file ++ target: ./examples/template/template.yaml ++ rules: ++ - allow: [Template] ++ ++ # Local example organizational data ++ - type: file ++ target: ./examples/org.yaml ++ rules: ++ - allow: [User, Group] +diff --git a/app-config.yaml b/app-config.yaml +index 1a622a2..ca52ec5 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/integrations/github/locations#configuration + 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 + +@@ -58,3 +66,5 @@ auth: + # see https://backstage.io/docs/auth/ to learn about auth providers +- providers: {} ++ providers: ++ # See https://backstage.io/docs/auth/guest/provider ++ guest: {} + +@@ -64,41 +74,40 @@ 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 ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml ++ rules: ++ - allow: [Template] + +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml ++ rules: ++ - allow: [User, Group] + +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml + +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.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] + +- # 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 +- 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 +- rules: +- - allow: [Template] ++kubernetes: ++ # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options ++ ++# see https://backstage.io/docs/permissions/getting-started for more on the permission framework ++permission: ++ # setting this to `false` will disable permissions ++ enabled: true +diff --git a/backstage.json b/backstage.json +index e91ab70..230d39d 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "0.4.14" ++ "version": "1.30.0" + } +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..80a963e 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.27.0", ++ "@backstage/e2e-test-utils": "^0.1.1", ++ "@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": "^10.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.5.4" ++ "typescript": "~5.4.0" ++ }, ++ "resolutions": { ++ "@types/react": "^18", ++ "@types/react-dom": "^18" + }, +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..839ff88 +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,27 @@ ++/* ++ * 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('/'); ++ ++ const enterButton = page.getByRole('button', { name: 'Enter' }); ++ await expect(enterButton).toBeVisible(); ++ await enterButton.click(); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 8ce600d..346ffc1 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,22 +5,37 @@ + "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.5.10", ++ "@backstage/catalog-model": "^1.6.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/core-app-api": "^1.14.2", ++ "@backstage/core-components": "^0.14.10", ++ "@backstage/core-plugin-api": "^1.9.3", ++ "@backstage/integration-react": "^1.1.30", ++ "@backstage/plugin-api-docs": "^0.11.8", ++ "@backstage/plugin-catalog": "^1.22.0", ++ "@backstage/plugin-catalog-common": "^1.0.26", ++ "@backstage/plugin-catalog-graph": "^0.4.8", ++ "@backstage/plugin-catalog-import": "^0.12.2", ++ "@backstage/plugin-catalog-react": "^1.12.3", ++ "@backstage/plugin-kubernetes": "^0.11.13", ++ "@backstage/plugin-org": "^0.6.28", ++ "@backstage/plugin-permission-react": "^0.4.25", ++ "@backstage/plugin-scaffolder": "^1.24.0", ++ "@backstage/plugin-search": "^1.4.15", ++ "@backstage/plugin-search-react": "^1.7.14", ++ "@backstage/plugin-techdocs": "^1.10.8", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.13", ++ "@backstage/plugin-techdocs-react": "^1.2.7", ++ "@backstage/plugin-user-settings": "^0.8.11", ++ "@backstage/theme": "^0.5.6", + "@material-ui/core": "^4.12.2", +@@ -28,31 +43,17 @@ + "history": "^5.0.0", +- "react": "^17.0.2", +- "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": "^18.0.2", ++ "react-dom": "^18.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", +- "@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", ++ "@backstage/test-utils": "^1.5.10", ++ "@playwright/test": "^1.32.3", ++ "@testing-library/jest-dom": "^6.0.0", ++ "@testing-library/react": "^14.0.0", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^9.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..18da7c4 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -8,5 +8,4 @@ + name="description" +- content="Backstage is an open platform for building developer portals" ++ content="Backstage is an open source framework for building developer portals" + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index b94cac7..ec8ba1d 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { renderWithEffects } from '@backstage/test-utils'; ++import { render, waitFor } from '@testing-library/react'; + import App from './App'; +@@ -22,4 +22,7 @@ describe('App', () => { + +- const rendered = await renderWithEffects(); +- expect(rendered.baseElement).toBeInTheDocument(); ++ const rendered = render(); ++ ++ await waitFor(() => { ++ expect(rendered.baseElement).toBeInTheDocument(); ++ }); + }); +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 78949b0..e24c468 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'; +@@ -15,5 +15,3 @@ import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +-import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -22,2 +20,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,5 +28,12 @@ import { Root } from './components/Root'; + +-import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; ++import { ++ AlertDisplay, ++ OAuthRequestDialog, ++ SignInPage, ++} 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 +45,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -44,2 +52,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -49,10 +58,10 @@ const app = createApp({ + }, ++ components: { ++ SignInPage: props => , ++ }, + }); + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -64,5 +73,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -74,6 +85,9 @@ const routes = ( + } ++ path="/catalog-import" ++ element={ ++ ++ ++ ++ } + /> +- } /> + }> +@@ -82,2 +96,3 @@ const routes = ( + } /> ++ } /> + +@@ -85,4 +100,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -92,5 +107,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..853d175 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,22 +1,5 @@ +-/* +- * 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'; + import ExtensionIcon from '@material-ui/icons/Extension'; +-import MapIcon from '@material-ui/icons/MyLocation'; + import LibraryBooks from '@material-ui/icons/LibraryBooks'; +@@ -25,3 +8,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -30,6 +12,3 @@ import { + } from '@backstage/plugin-user-settings'; +-import { +- SidebarSearchModal, +- SearchContextProvider, +-} from '@backstage/plugin-search'; ++import { SidebarSearchModal } from '@backstage/plugin-search'; + import { +@@ -37,3 +16,2 @@ import { + sidebarConfig, +- SidebarContext, + SidebarDivider, +@@ -44,2 +22,4 @@ import { + SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -47,2 +27,4 @@ import MenuIcon from '@material-ui/icons/Menu'; + import SearchIcon from '@material-ui/icons/Search'; ++import { MyGroupsSidebarItem } from '@backstage/plugin-org'; ++import GroupIcon from '@material-ui/icons/People'; + +@@ -65,3 +47,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -69,8 +51,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -86,5 +63,3 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + } to="/search"> +- +- +- {' '} ++ + +@@ -94,2 +69,7 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + ++ + +@@ -100,3 +80,3 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ {/* Items in this group will be scrollable if they run out of space */} + +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..6164c76 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,7 +27,5 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +-import { +- isGithubActionsAvailable, +- EntityGithubActionsContent, +-} from '@backstage/plugin-github-actions'; + import { +@@ -56,2 +38,32 @@ 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'; ++ ++import { ++ EntityKubernetesContent, ++ isKubernetesAvailable, ++} from '@backstage/plugin-kubernetes'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); + +@@ -61,5 +73,9 @@ const cicdContent = ( + +- +- +- ++ {/* ++ Here you can add support for different CI/CD services, for example ++ using @backstage-community/plugin-github-actions as follows: ++ ++ ++ ++ */} + +@@ -94,2 +110,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -110,2 +134,6 @@ const overviewContent = ( + ++ ++ ++ ++ + +@@ -129,2 +157,10 @@ const serviceEntityPage = ( + ++ ++ ++ ++ + +@@ -152,3 +188,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -167,2 +203,10 @@ const websiteEntityPage = ( + ++ ++ ++ ++ + +@@ -179,3 +223,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -198,3 +242,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -225,2 +269,5 @@ const apiPage = ( + ++ ++ ++ + +@@ -276,5 +323,8 @@ const groupPage = ( + +- ++ + + ++ ++ ++ + +@@ -292,3 +342,9 @@ const systemPage = ( + +- ++ ++ ++ ++ ++ ++ ++ + +@@ -304,3 +360,19 @@ const systemPage = ( + +- ++ + +@@ -317,2 +389,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/app/src/index.tsx b/packages/app/src/index.tsx +index b16aaf7..d875c77 100644 +--- a/packages/app/src/index.tsx ++++ b/packages/app/src/index.tsx +@@ -2,5 +2,5 @@ import '@backstage/cli/asset-types'; + import React from 'react'; +-import ReactDOM from 'react-dom'; ++import ReactDOM from 'react-dom/client'; + import App from './App'; + +-ReactDOM.render(, document.getElementById('root')); ++ReactDOM.createRoot(document.getElementById('root')!).render(); +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..bef67b5 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,15 @@ 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 ++ ++# This will include the examples, if you don't need these simply remove this line ++COPY --chown=node:node examples ./examples + + # 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..6a14abc 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,35 +6,43 @@ + "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", +- "dockerode": "^3.3.1", +- "express": "^4.17.1", +- "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "@backstage/backend-common": "^0.24.0", ++ "@backstage/backend-defaults": "^0.4.2", ++ "@backstage/backend-tasks": "^0.6.0", ++ "@backstage/config": "^1.2.0", ++ "@backstage/plugin-app-backend": "^0.3.72", ++ "@backstage/plugin-auth-backend": "^0.22.10", ++ "@backstage/plugin-auth-backend-module-github-provider": "^0.1.20", ++ "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.9", ++ "@backstage/plugin-auth-node": "^0.5.0", ++ "@backstage/plugin-catalog-backend": "^1.25.0", ++ "@backstage/plugin-catalog-backend-module-logs": "^0.0.2", ++ "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.21", ++ "@backstage/plugin-kubernetes-backend": "^0.18.4", ++ "@backstage/plugin-permission-backend": "^0.5.47", ++ "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.1.20", ++ "@backstage/plugin-permission-common": "^0.8.1", ++ "@backstage/plugin-permission-node": "^0.8.1", ++ "@backstage/plugin-proxy-backend": "^0.5.4", ++ "@backstage/plugin-scaffolder-backend": "^1.24.0", ++ "@backstage/plugin-search-backend": "^1.5.15", ++ "@backstage/plugin-search-backend-module-catalog": "^0.2.0", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.33", ++ "@backstage/plugin-search-backend-module-techdocs": "^0.2.0", ++ "@backstage/plugin-search-backend-node": "^1.3.0", ++ "@backstage/plugin-techdocs-backend": "^1.10.10", ++ "app": "link:../app", ++ "better-sqlite3": "^9.0.0", ++ "node-gyp": "^10.0.0", ++ "pg": "^8.11.3", + "winston": "^3.2.1" +@@ -42,6 +50,6 @@ + "devDependencies": { +- "@backstage/cli": "^0.12.0", +- "@types/dockerode": "^3.3.0", ++ "@backstage/cli": "^0.27.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.test.ts b/packages/backend/src/index.test.ts +deleted file mode 100644 +index 7814b8c..0000000 +--- a/packages/backend/src/index.test.ts ++++ /dev/null +@@ -1,8 +0,0 @@ +-import { PluginEnvironment } from './types'; +- +-describe('test', () => { +- it('unbreaks the test runner', () => { +- const unbreaker = {} as PluginEnvironment; +- expect(unbreaker).toBeTruthy(); +- }); +-}); +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 70bc66b..f57d4c5 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -8,102 +8,47 @@ + +-import Router from 'express-promise-router'; +-import { +- createServiceBuilder, +- loadBackendConfig, +- getRootLogger, +- useHotMemoize, +- notFoundHandler, +- CacheManager, +- DatabaseManager, +- SingleHostDiscovery, +- UrlReaders, +- ServerTokenManager, +-} from '@backstage/backend-common'; +-import { TaskScheduler } from '@backstage/backend-tasks'; +-import { Config } from '@backstage/config'; +-import app from './plugins/app'; +-import auth from './plugins/auth'; +-import catalog from './plugins/catalog'; +-import scaffolder from './plugins/scaffolder'; +-import proxy from './plugins/proxy'; +-import techdocs from './plugins/techdocs'; +-import search from './plugins/search'; +-import { PluginEnvironment } from './types'; +-import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { createBackend } from '@backstage/backend-defaults'; + +-function makeCreateEnv(config: Config) { +- const root = getRootLogger(); +- const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); +- const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); +- const permissions = ServerPermissionClient.fromConfig(config, { +- discovery, +- tokenManager, +- }); ++const backend = createBackend(); + +- root.info(`Created UrlReader ${reader}`); ++backend.add(import('@backstage/plugin-app-backend/alpha')); ++backend.add(import('@backstage/plugin-proxy-backend/alpha')); ++backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); ++backend.add(import('@backstage/plugin-techdocs-backend/alpha')); + +- return (plugin: string): PluginEnvironment => { +- const logger = root.child({ type: 'plugin', plugin }); +- const database = databaseManager.forPlugin(plugin); +- const cache = cacheManager.forPlugin(plugin); +- const scheduler = taskScheduler.forPlugin(plugin); +- return { +- logger, +- database, +- cache, +- config, +- reader, +- discovery, +- tokenManager, +- scheduler, +- permissions, +- }; +- }; +-} ++// auth plugin ++backend.add(import('@backstage/plugin-auth-backend')); ++// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin ++backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); ++// See https://backstage.io/docs/auth/guest/provider + +-async function main() { +- const config = await loadBackendConfig({ +- argv: process.argv, +- logger: getRootLogger(), +- }); +- const createEnv = makeCreateEnv(config); ++// catalog plugin ++backend.add(import('@backstage/plugin-catalog-backend/alpha')); ++backend.add( ++ import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ++); + +- const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); +- const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); +- const authEnv = useHotMemoize(module, () => createEnv('auth')); +- const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); +- const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); +- const searchEnv = useHotMemoize(module, () => createEnv('search')); +- const appEnv = useHotMemoize(module, () => createEnv('app')); ++// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors ++backend.add(import('@backstage/plugin-catalog-backend-module-logs')); + +- const apiRouter = Router(); +- apiRouter.use('/catalog', await catalog(catalogEnv)); +- apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); +- apiRouter.use('/auth', await auth(authEnv)); +- apiRouter.use('/techdocs', await techdocs(techdocsEnv)); +- apiRouter.use('/proxy', await proxy(proxyEnv)); +- apiRouter.use('/search', await search(searchEnv)); ++// permission plugin ++backend.add(import('@backstage/plugin-permission-backend/alpha')); ++// See https://backstage.io/docs/permissions/getting-started for how to create your own permission policy ++backend.add( ++ import('@backstage/plugin-permission-backend-module-allow-all-policy'), ++); + +- // Add backends ABOVE this line; this 404 handler is the catch-all fallback +- apiRouter.use(notFoundHandler()); ++// search plugin ++backend.add(import('@backstage/plugin-search-backend/alpha')); + +- const service = createServiceBuilder(module) +- .loadConfig(config) +- .addRouter('/api', apiRouter) +- .addRouter('', await app(appEnv)); ++// search engine ++// See https://backstage.io/docs/features/search/search-engines ++backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); + +- await service.start().catch(err => { +- console.log(err); +- process.exit(1); +- }); +-} ++// search collators ++backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); ++backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); + +-module.hot?.accept(); +-main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); +- process.exit(1); +-}); ++// kubernetes ++backend.add(import('@backstage/plugin-kubernetes-backend/alpha')); ++ ++backend.start(); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +deleted file mode 100644 +index 14e19a1..0000000 +--- a/packages/backend/src/plugins/app.ts ++++ /dev/null +@@ -1,16 +0,0 @@ +-import { createRouter } from '@backstage/plugin-app-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- database, +-}: PluginEnvironment): Promise { +- return await createRouter({ +- logger, +- config, +- database, +- appPackageName: 'app', +- }); +-} +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +deleted file mode 100644 +index 5216510..0000000 +--- a/packages/backend/src/plugins/auth.ts ++++ /dev/null +@@ -1,12 +0,0 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, database, discovery }); +-} +diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts +deleted file mode 100644 +index 876cb6b..0000000 +--- a/packages/backend/src/plugins/catalog.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +-import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const builder = await CatalogBuilder.create(env); +- builder.addProcessor(new ScaffolderEntitiesProcessor()); +- const { processingEngine, router } = await builder.build(); +- await processingEngine.start(); +- return router; +-} +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +deleted file mode 100644 +index 506f6d9..0000000 +--- a/packages/backend/src/plugins/proxy.ts ++++ /dev/null +@@ -1,11 +0,0 @@ +-import { createRouter } from '@backstage/plugin-proxy-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); +-} +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +deleted file mode 100644 +index 6be2e97..0000000 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ /dev/null +@@ -1,27 +0,0 @@ +-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'; +-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 }); +- +- return await createRouter({ +- containerRunner, +- logger, +- config, +- database, +- catalogClient, +- reader, +- }); +-} +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +deleted file mode 100644 +index f23b0c7..0000000 +--- a/packages/backend/src/plugins/search.ts ++++ /dev/null +@@ -1,54 +0,0 @@ +-import { useHotCleanup } from '@backstage/backend-common'; +-import { createRouter } from '@backstage/plugin-search-backend'; +-import { +- IndexBuilder, +- LunrSearchEngine, +-} from '@backstage/plugin-search-backend-node'; +-import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend'; +- +-export default async function createPlugin({ +- logger, +- discovery, +- config, +- tokenManager, +-}: PluginEnvironment) { +- // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); +- +- // Collators are responsible for gathering documents known to plugins. This +- // collator gathers entities from the software catalog. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { +- discovery, +- tokenManager, +- }), +- }); +- +- // collator gathers entities from techdocs. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultTechDocsCollator.fromConfig(config, { +- discovery, +- logger, +- tokenManager, +- }), +- }); +- +- // The scheduler controls when documents are gathered from collators and sent +- // to the search engine for indexing. +- const { scheduler } = await indexBuilder.build(); +- +- // 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()); +- +- return await createRouter({ +- engine: indexBuilder.getSearchEngine(), +- logger, +- }); +-} +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +deleted file mode 100644 +index 054c64d..0000000 +--- a/packages/backend/src/plugins/techdocs.ts ++++ /dev/null +@@ -1,55 +0,0 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; +-import { +- createRouter, +- Generators, +- Preparers, +- Publisher, +-} from '@backstage/plugin-techdocs-backend'; +-import Docker from 'dockerode'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +- cache, +-}: PluginEnvironment): Promise { +- // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, +- }); +- +- // Docker client (conditionally) used by the generators, based on techdocs.generators config. +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- +- // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, +- containerRunner, +- }); +- +- // Publisher is used for +- // 1. Publishing generated files to storage +- // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, +- }); +- +- // checks if the publisher is working and logs the result +- await publisher.getReadiness(); +- +- return await createRouter({ +- preparers, +- generators, +- publisher, +- logger, +- config, +- discovery, +- cache, +- }); +-} +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +deleted file mode 100644 +index 0862b0e..0000000 +--- a/packages/backend/src/types.ts ++++ /dev/null +@@ -1,23 +0,0 @@ +-import { Logger } from 'winston'; +-import { Config } from '@backstage/config'; +-import { +- PluginCacheManager, +- PluginDatabaseManager, +- PluginEndpointDiscovery, +- TokenManager, +- UrlReader, +-} from '@backstage/backend-common'; +-import { PluginTaskScheduler } from '@backstage/backend-tasks'; +-import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; +- +-export type PluginEnvironment = { +- logger: Logger; +- database: PluginDatabaseManager; +- cache: PluginCacheManager; +- config: Config; +- reader: UrlReader; +- discovery: PluginEndpointDiscovery; +- tokenManager: TokenManager; +- scheduler: PluginTaskScheduler; +- permissions: PermissionAuthorizer; +-}; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..733be13 +--- /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 dev', ++ 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..c37fc61 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1,3 @@ ++# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. ++# yarn lockfile v1 ++ diff --git a/diffs/0.4.15..0.5.18.diff b/diffs/0.4.15..0.5.18.diff new file mode 100644 index 00000000..f95ca648 --- /dev/null +++ b/diffs/0.4.15..0.5.18.diff @@ -0,0 +1,1940 @@ +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..5d426f5 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,51 @@ 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/apis/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 ++ ++auth: ++ providers: ++ guest: {} ++ ++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: ++ # Local example data, replace this with your production config, these are intended for demo use only. ++ # File locations are relative to the backend process, typically in a deployed context, such as in a Docker container, this will be the root ++ - type: file ++ target: ./examples/entities.yaml ++ ++ # Local example template ++ - type: file ++ target: ./examples/template/template.yaml ++ rules: ++ - allow: [Template] ++ ++ # Local example organizational data ++ - type: file ++ target: ./examples/org.yaml ++ rules: ++ - allow: [User, Group] +diff --git a/app-config.yaml b/app-config.yaml +index 1a622a2..ca52ec5 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/integrations/github/locations#configuration + 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 + +@@ -58,3 +66,5 @@ auth: + # see https://backstage.io/docs/auth/ to learn about auth providers +- providers: {} ++ providers: ++ # See https://backstage.io/docs/auth/guest/provider ++ guest: {} + +@@ -64,41 +74,40 @@ 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 ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml ++ rules: ++ - allow: [Template] + +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml ++ rules: ++ - allow: [User, Group] + +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml + +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.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] + +- # 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 +- 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 +- rules: +- - allow: [Template] ++kubernetes: ++ # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options ++ ++# see https://backstage.io/docs/permissions/getting-started for more on the permission framework ++permission: ++ # setting this to `false` will disable permissions ++ enabled: true +diff --git a/backstage.json b/backstage.json +index e27bbc7..230d39d 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "0.4.15" ++ "version": "1.30.0" + } +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..80a963e 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.27.0", ++ "@backstage/e2e-test-utils": "^0.1.1", ++ "@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": "^10.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.5.4" ++ "typescript": "~5.4.0" ++ }, ++ "resolutions": { ++ "@types/react": "^18", ++ "@types/react-dom": "^18" + }, +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..839ff88 +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,27 @@ ++/* ++ * 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('/'); ++ ++ const enterButton = page.getByRole('button', { name: 'Enter' }); ++ await expect(enterButton).toBeVisible(); ++ await enterButton.click(); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 1681062..346ffc1 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,23 +5,37 @@ + "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.5.10", ++ "@backstage/catalog-model": "^1.6.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/core-app-api": "^1.14.2", ++ "@backstage/core-components": "^0.14.10", ++ "@backstage/core-plugin-api": "^1.9.3", ++ "@backstage/integration-react": "^1.1.30", ++ "@backstage/plugin-api-docs": "^0.11.8", ++ "@backstage/plugin-catalog": "^1.22.0", ++ "@backstage/plugin-catalog-common": "^1.0.26", ++ "@backstage/plugin-catalog-graph": "^0.4.8", ++ "@backstage/plugin-catalog-import": "^0.12.2", ++ "@backstage/plugin-catalog-react": "^1.12.3", ++ "@backstage/plugin-kubernetes": "^0.11.13", ++ "@backstage/plugin-org": "^0.6.28", ++ "@backstage/plugin-permission-react": "^0.4.25", ++ "@backstage/plugin-scaffolder": "^1.24.0", ++ "@backstage/plugin-search": "^1.4.15", ++ "@backstage/plugin-search-react": "^1.7.14", ++ "@backstage/plugin-techdocs": "^1.10.8", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.13", ++ "@backstage/plugin-techdocs-react": "^1.2.7", ++ "@backstage/plugin-user-settings": "^0.8.11", ++ "@backstage/theme": "^0.5.6", + "@material-ui/core": "^4.12.2", +@@ -29,31 +43,17 @@ + "history": "^5.0.0", +- "react": "^17.0.2", +- "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": "^18.0.2", ++ "react-dom": "^18.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", +- "@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", ++ "@backstage/test-utils": "^1.5.10", ++ "@playwright/test": "^1.32.3", ++ "@testing-library/jest-dom": "^6.0.0", ++ "@testing-library/react": "^14.0.0", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^9.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..18da7c4 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -8,5 +8,4 @@ + name="description" +- content="Backstage is an open platform for building developer portals" ++ content="Backstage is an open source framework for building developer portals" + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index b94cac7..ec8ba1d 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { renderWithEffects } from '@backstage/test-utils'; ++import { render, waitFor } from '@testing-library/react'; + import App from './App'; +@@ -22,4 +22,7 @@ describe('App', () => { + +- const rendered = await renderWithEffects(); +- expect(rendered.baseElement).toBeInTheDocument(); ++ const rendered = render(); ++ ++ await waitFor(() => { ++ expect(rendered.baseElement).toBeInTheDocument(); ++ }); + }); +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 78949b0..e24c468 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'; +@@ -15,5 +15,3 @@ import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +-import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -22,2 +20,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,5 +28,12 @@ import { Root } from './components/Root'; + +-import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; ++import { ++ AlertDisplay, ++ OAuthRequestDialog, ++ SignInPage, ++} 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 +45,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -44,2 +52,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -49,10 +58,10 @@ const app = createApp({ + }, ++ components: { ++ SignInPage: props => , ++ }, + }); + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -64,5 +73,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -74,6 +85,9 @@ const routes = ( + } ++ path="/catalog-import" ++ element={ ++ ++ ++ ++ } + /> +- } /> + }> +@@ -82,2 +96,3 @@ const routes = ( + } /> ++ } /> + +@@ -85,4 +100,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -92,5 +107,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..853d175 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,22 +1,5 @@ +-/* +- * 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'; + import ExtensionIcon from '@material-ui/icons/Extension'; +-import MapIcon from '@material-ui/icons/MyLocation'; + import LibraryBooks from '@material-ui/icons/LibraryBooks'; +@@ -25,3 +8,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -30,6 +12,3 @@ import { + } from '@backstage/plugin-user-settings'; +-import { +- SidebarSearchModal, +- SearchContextProvider, +-} from '@backstage/plugin-search'; ++import { SidebarSearchModal } from '@backstage/plugin-search'; + import { +@@ -37,3 +16,2 @@ import { + sidebarConfig, +- SidebarContext, + SidebarDivider, +@@ -44,2 +22,4 @@ import { + SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -47,2 +27,4 @@ import MenuIcon from '@material-ui/icons/Menu'; + import SearchIcon from '@material-ui/icons/Search'; ++import { MyGroupsSidebarItem } from '@backstage/plugin-org'; ++import GroupIcon from '@material-ui/icons/People'; + +@@ -65,3 +47,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -69,8 +51,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -86,5 +63,3 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + } to="/search"> +- +- +- {' '} ++ + +@@ -94,2 +69,7 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + ++ + +@@ -100,3 +80,3 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ {/* Items in this group will be scrollable if they run out of space */} + +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..6164c76 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,7 +27,5 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +-import { +- isGithubActionsAvailable, +- EntityGithubActionsContent, +-} from '@backstage/plugin-github-actions'; + import { +@@ -70,2 +53,18 @@ import { + ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++import { ++ EntityKubernetesContent, ++ isKubernetesAvailable, ++} from '@backstage/plugin-kubernetes'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); ++ + const cicdContent = ( +@@ -74,5 +73,9 @@ const cicdContent = ( + +- +- +- ++ {/* ++ Here you can add support for different CI/CD services, for example ++ using @backstage-community/plugin-github-actions as follows: ++ ++ ++ ++ */} + +@@ -107,2 +110,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -146,2 +157,10 @@ const serviceEntityPage = ( + ++ ++ ++ ++ + +@@ -169,3 +188,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -184,2 +203,10 @@ const websiteEntityPage = ( + ++ ++ ++ ++ + +@@ -196,3 +223,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -215,3 +242,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -296,5 +323,8 @@ const groupPage = ( + +- ++ + + ++ ++ ++ + +@@ -315,3 +345,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/app/src/index.tsx b/packages/app/src/index.tsx +index b16aaf7..d875c77 100644 +--- a/packages/app/src/index.tsx ++++ b/packages/app/src/index.tsx +@@ -2,5 +2,5 @@ import '@backstage/cli/asset-types'; + import React from 'react'; +-import ReactDOM from 'react-dom'; ++import ReactDOM from 'react-dom/client'; + import App from './App'; + +-ReactDOM.render(, document.getElementById('root')); ++ReactDOM.createRoot(document.getElementById('root')!).render(); +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..bef67b5 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,15 @@ 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 ++ ++# This will include the examples, if you don't need these simply remove this line ++COPY --chown=node:node examples ./examples + + # 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..6a14abc 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,35 +6,43 @@ + "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", +- "dockerode": "^3.3.1", +- "express": "^4.17.1", +- "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "@backstage/backend-common": "^0.24.0", ++ "@backstage/backend-defaults": "^0.4.2", ++ "@backstage/backend-tasks": "^0.6.0", ++ "@backstage/config": "^1.2.0", ++ "@backstage/plugin-app-backend": "^0.3.72", ++ "@backstage/plugin-auth-backend": "^0.22.10", ++ "@backstage/plugin-auth-backend-module-github-provider": "^0.1.20", ++ "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.9", ++ "@backstage/plugin-auth-node": "^0.5.0", ++ "@backstage/plugin-catalog-backend": "^1.25.0", ++ "@backstage/plugin-catalog-backend-module-logs": "^0.0.2", ++ "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.21", ++ "@backstage/plugin-kubernetes-backend": "^0.18.4", ++ "@backstage/plugin-permission-backend": "^0.5.47", ++ "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.1.20", ++ "@backstage/plugin-permission-common": "^0.8.1", ++ "@backstage/plugin-permission-node": "^0.8.1", ++ "@backstage/plugin-proxy-backend": "^0.5.4", ++ "@backstage/plugin-scaffolder-backend": "^1.24.0", ++ "@backstage/plugin-search-backend": "^1.5.15", ++ "@backstage/plugin-search-backend-module-catalog": "^0.2.0", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.33", ++ "@backstage/plugin-search-backend-module-techdocs": "^0.2.0", ++ "@backstage/plugin-search-backend-node": "^1.3.0", ++ "@backstage/plugin-techdocs-backend": "^1.10.10", ++ "app": "link:../app", ++ "better-sqlite3": "^9.0.0", ++ "node-gyp": "^10.0.0", ++ "pg": "^8.11.3", + "winston": "^3.2.1" +@@ -42,6 +50,6 @@ + "devDependencies": { +- "@backstage/cli": "^0.13.0", +- "@types/dockerode": "^3.3.0", ++ "@backstage/cli": "^0.27.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.test.ts b/packages/backend/src/index.test.ts +deleted file mode 100644 +index 7814b8c..0000000 +--- a/packages/backend/src/index.test.ts ++++ /dev/null +@@ -1,8 +0,0 @@ +-import { PluginEnvironment } from './types'; +- +-describe('test', () => { +- it('unbreaks the test runner', () => { +- const unbreaker = {} as PluginEnvironment; +- expect(unbreaker).toBeTruthy(); +- }); +-}); +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 70bc66b..f57d4c5 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -8,102 +8,47 @@ + +-import Router from 'express-promise-router'; +-import { +- createServiceBuilder, +- loadBackendConfig, +- getRootLogger, +- useHotMemoize, +- notFoundHandler, +- CacheManager, +- DatabaseManager, +- SingleHostDiscovery, +- UrlReaders, +- ServerTokenManager, +-} from '@backstage/backend-common'; +-import { TaskScheduler } from '@backstage/backend-tasks'; +-import { Config } from '@backstage/config'; +-import app from './plugins/app'; +-import auth from './plugins/auth'; +-import catalog from './plugins/catalog'; +-import scaffolder from './plugins/scaffolder'; +-import proxy from './plugins/proxy'; +-import techdocs from './plugins/techdocs'; +-import search from './plugins/search'; +-import { PluginEnvironment } from './types'; +-import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { createBackend } from '@backstage/backend-defaults'; + +-function makeCreateEnv(config: Config) { +- const root = getRootLogger(); +- const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); +- const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); +- const permissions = ServerPermissionClient.fromConfig(config, { +- discovery, +- tokenManager, +- }); ++const backend = createBackend(); + +- root.info(`Created UrlReader ${reader}`); ++backend.add(import('@backstage/plugin-app-backend/alpha')); ++backend.add(import('@backstage/plugin-proxy-backend/alpha')); ++backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); ++backend.add(import('@backstage/plugin-techdocs-backend/alpha')); + +- return (plugin: string): PluginEnvironment => { +- const logger = root.child({ type: 'plugin', plugin }); +- const database = databaseManager.forPlugin(plugin); +- const cache = cacheManager.forPlugin(plugin); +- const scheduler = taskScheduler.forPlugin(plugin); +- return { +- logger, +- database, +- cache, +- config, +- reader, +- discovery, +- tokenManager, +- scheduler, +- permissions, +- }; +- }; +-} ++// auth plugin ++backend.add(import('@backstage/plugin-auth-backend')); ++// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin ++backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); ++// See https://backstage.io/docs/auth/guest/provider + +-async function main() { +- const config = await loadBackendConfig({ +- argv: process.argv, +- logger: getRootLogger(), +- }); +- const createEnv = makeCreateEnv(config); ++// catalog plugin ++backend.add(import('@backstage/plugin-catalog-backend/alpha')); ++backend.add( ++ import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ++); + +- const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); +- const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); +- const authEnv = useHotMemoize(module, () => createEnv('auth')); +- const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); +- const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); +- const searchEnv = useHotMemoize(module, () => createEnv('search')); +- const appEnv = useHotMemoize(module, () => createEnv('app')); ++// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors ++backend.add(import('@backstage/plugin-catalog-backend-module-logs')); + +- const apiRouter = Router(); +- apiRouter.use('/catalog', await catalog(catalogEnv)); +- apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); +- apiRouter.use('/auth', await auth(authEnv)); +- apiRouter.use('/techdocs', await techdocs(techdocsEnv)); +- apiRouter.use('/proxy', await proxy(proxyEnv)); +- apiRouter.use('/search', await search(searchEnv)); ++// permission plugin ++backend.add(import('@backstage/plugin-permission-backend/alpha')); ++// See https://backstage.io/docs/permissions/getting-started for how to create your own permission policy ++backend.add( ++ import('@backstage/plugin-permission-backend-module-allow-all-policy'), ++); + +- // Add backends ABOVE this line; this 404 handler is the catch-all fallback +- apiRouter.use(notFoundHandler()); ++// search plugin ++backend.add(import('@backstage/plugin-search-backend/alpha')); + +- const service = createServiceBuilder(module) +- .loadConfig(config) +- .addRouter('/api', apiRouter) +- .addRouter('', await app(appEnv)); ++// search engine ++// See https://backstage.io/docs/features/search/search-engines ++backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); + +- await service.start().catch(err => { +- console.log(err); +- process.exit(1); +- }); +-} ++// search collators ++backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); ++backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); + +-module.hot?.accept(); +-main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); +- process.exit(1); +-}); ++// kubernetes ++backend.add(import('@backstage/plugin-kubernetes-backend/alpha')); ++ ++backend.start(); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +deleted file mode 100644 +index 14e19a1..0000000 +--- a/packages/backend/src/plugins/app.ts ++++ /dev/null +@@ -1,16 +0,0 @@ +-import { createRouter } from '@backstage/plugin-app-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- database, +-}: PluginEnvironment): Promise { +- return await createRouter({ +- logger, +- config, +- database, +- appPackageName: 'app', +- }); +-} +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +deleted file mode 100644 +index 5216510..0000000 +--- a/packages/backend/src/plugins/auth.ts ++++ /dev/null +@@ -1,12 +0,0 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, database, discovery }); +-} +diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts +deleted file mode 100644 +index 876cb6b..0000000 +--- a/packages/backend/src/plugins/catalog.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +-import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const builder = await CatalogBuilder.create(env); +- builder.addProcessor(new ScaffolderEntitiesProcessor()); +- const { processingEngine, router } = await builder.build(); +- await processingEngine.start(); +- return router; +-} +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +deleted file mode 100644 +index 506f6d9..0000000 +--- a/packages/backend/src/plugins/proxy.ts ++++ /dev/null +@@ -1,11 +0,0 @@ +-import { createRouter } from '@backstage/plugin-proxy-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); +-} +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +deleted file mode 100644 +index 6be2e97..0000000 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ /dev/null +@@ -1,27 +0,0 @@ +-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'; +-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 }); +- +- return await createRouter({ +- containerRunner, +- logger, +- config, +- database, +- catalogClient, +- reader, +- }); +-} +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +deleted file mode 100644 +index a0a1cc3..0000000 +--- a/packages/backend/src/plugins/search.ts ++++ /dev/null +@@ -1,58 +0,0 @@ +-import { useHotCleanup } from '@backstage/backend-common'; +-import { createRouter } from '@backstage/plugin-search-backend'; +-import { +- IndexBuilder, +- LunrSearchEngine, +-} from '@backstage/plugin-search-backend-node'; +-import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend'; +- +-export default async function createPlugin({ +- logger, +- permissions, +- discovery, +- config, +- tokenManager, +-}: PluginEnvironment) { +- // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); +- +- // Collators are responsible for gathering documents known to plugins. This +- // collator gathers entities from the software catalog. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { +- discovery, +- tokenManager, +- }), +- }); +- +- // collator gathers entities from techdocs. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultTechDocsCollator.fromConfig(config, { +- discovery, +- logger, +- tokenManager, +- }), +- }); +- +- // The scheduler controls when documents are gathered from collators and sent +- // to the search engine for indexing. +- const { scheduler } = await indexBuilder.build(); +- +- // 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()); +- +- return await createRouter({ +- engine: indexBuilder.getSearchEngine(), +- types: indexBuilder.getDocumentTypes(), +- permissions, +- config, +- logger, +- }); +-} +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +deleted file mode 100644 +index 054c64d..0000000 +--- a/packages/backend/src/plugins/techdocs.ts ++++ /dev/null +@@ -1,55 +0,0 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; +-import { +- createRouter, +- Generators, +- Preparers, +- Publisher, +-} from '@backstage/plugin-techdocs-backend'; +-import Docker from 'dockerode'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +- cache, +-}: PluginEnvironment): Promise { +- // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, +- }); +- +- // Docker client (conditionally) used by the generators, based on techdocs.generators config. +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- +- // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, +- containerRunner, +- }); +- +- // Publisher is used for +- // 1. Publishing generated files to storage +- // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, +- }); +- +- // checks if the publisher is working and logs the result +- await publisher.getReadiness(); +- +- return await createRouter({ +- preparers, +- generators, +- publisher, +- logger, +- config, +- discovery, +- cache, +- }); +-} +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +deleted file mode 100644 +index 0862b0e..0000000 +--- a/packages/backend/src/types.ts ++++ /dev/null +@@ -1,23 +0,0 @@ +-import { Logger } from 'winston'; +-import { Config } from '@backstage/config'; +-import { +- PluginCacheManager, +- PluginDatabaseManager, +- PluginEndpointDiscovery, +- TokenManager, +- UrlReader, +-} from '@backstage/backend-common'; +-import { PluginTaskScheduler } from '@backstage/backend-tasks'; +-import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; +- +-export type PluginEnvironment = { +- logger: Logger; +- database: PluginDatabaseManager; +- cache: PluginCacheManager; +- config: Config; +- reader: UrlReader; +- discovery: PluginEndpointDiscovery; +- tokenManager: TokenManager; +- scheduler: PluginTaskScheduler; +- permissions: PermissionAuthorizer; +-}; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..733be13 +--- /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 dev', ++ 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..c37fc61 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1,3 @@ ++# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. ++# yarn lockfile v1 ++ diff --git a/diffs/0.4.16..0.5.18.diff b/diffs/0.4.16..0.5.18.diff new file mode 100644 index 00000000..9b6e624c --- /dev/null +++ b/diffs/0.4.16..0.5.18.diff @@ -0,0 +1,1936 @@ +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..5d426f5 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,51 @@ 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/apis/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 ++ ++auth: ++ providers: ++ guest: {} ++ ++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: ++ # Local example data, replace this with your production config, these are intended for demo use only. ++ # File locations are relative to the backend process, typically in a deployed context, such as in a Docker container, this will be the root ++ - type: file ++ target: ./examples/entities.yaml ++ ++ # Local example template ++ - type: file ++ target: ./examples/template/template.yaml ++ rules: ++ - allow: [Template] ++ ++ # Local example organizational data ++ - type: file ++ target: ./examples/org.yaml ++ rules: ++ - allow: [User, Group] +diff --git a/app-config.yaml b/app-config.yaml +index 1a622a2..ca52ec5 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/integrations/github/locations#configuration + 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 + +@@ -58,3 +66,5 @@ auth: + # see https://backstage.io/docs/auth/ to learn about auth providers +- providers: {} ++ providers: ++ # See https://backstage.io/docs/auth/guest/provider ++ guest: {} + +@@ -64,41 +74,40 @@ 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 ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml ++ rules: ++ - allow: [Template] + +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml ++ rules: ++ - allow: [User, Group] + +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml + +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.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] + +- # 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 +- 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 +- rules: +- - allow: [Template] ++kubernetes: ++ # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options ++ ++# see https://backstage.io/docs/permissions/getting-started for more on the permission framework ++permission: ++ # setting this to `false` will disable permissions ++ enabled: true +diff --git a/backstage.json b/backstage.json +index 5c3e9d4..230d39d 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "0.4.16" ++ "version": "1.30.0" + } +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..80a963e 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.27.0", ++ "@backstage/e2e-test-utils": "^0.1.1", ++ "@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": "^10.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.5.4" ++ "typescript": "~5.4.0" ++ }, ++ "resolutions": { ++ "@types/react": "^18", ++ "@types/react-dom": "^18" + }, +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..839ff88 +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,27 @@ ++/* ++ * 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('/'); ++ ++ const enterButton = page.getByRole('button', { name: 'Enter' }); ++ await expect(enterButton).toBeVisible(); ++ await enterButton.click(); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 1681062..346ffc1 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,23 +5,37 @@ + "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.5.10", ++ "@backstage/catalog-model": "^1.6.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/core-app-api": "^1.14.2", ++ "@backstage/core-components": "^0.14.10", ++ "@backstage/core-plugin-api": "^1.9.3", ++ "@backstage/integration-react": "^1.1.30", ++ "@backstage/plugin-api-docs": "^0.11.8", ++ "@backstage/plugin-catalog": "^1.22.0", ++ "@backstage/plugin-catalog-common": "^1.0.26", ++ "@backstage/plugin-catalog-graph": "^0.4.8", ++ "@backstage/plugin-catalog-import": "^0.12.2", ++ "@backstage/plugin-catalog-react": "^1.12.3", ++ "@backstage/plugin-kubernetes": "^0.11.13", ++ "@backstage/plugin-org": "^0.6.28", ++ "@backstage/plugin-permission-react": "^0.4.25", ++ "@backstage/plugin-scaffolder": "^1.24.0", ++ "@backstage/plugin-search": "^1.4.15", ++ "@backstage/plugin-search-react": "^1.7.14", ++ "@backstage/plugin-techdocs": "^1.10.8", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.13", ++ "@backstage/plugin-techdocs-react": "^1.2.7", ++ "@backstage/plugin-user-settings": "^0.8.11", ++ "@backstage/theme": "^0.5.6", + "@material-ui/core": "^4.12.2", +@@ -29,31 +43,17 @@ + "history": "^5.0.0", +- "react": "^17.0.2", +- "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": "^18.0.2", ++ "react-dom": "^18.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", +- "@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", ++ "@backstage/test-utils": "^1.5.10", ++ "@playwright/test": "^1.32.3", ++ "@testing-library/jest-dom": "^6.0.0", ++ "@testing-library/react": "^14.0.0", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^9.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..18da7c4 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -8,5 +8,4 @@ + name="description" +- content="Backstage is an open platform for building developer portals" ++ content="Backstage is an open source framework for building developer portals" + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index b94cac7..ec8ba1d 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { renderWithEffects } from '@backstage/test-utils'; ++import { render, waitFor } from '@testing-library/react'; + import App from './App'; +@@ -22,4 +22,7 @@ describe('App', () => { + +- const rendered = await renderWithEffects(); +- expect(rendered.baseElement).toBeInTheDocument(); ++ const rendered = render(); ++ ++ await waitFor(() => { ++ expect(rendered.baseElement).toBeInTheDocument(); ++ }); + }); +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 5a379b5..e24c468 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'; +@@ -15,5 +15,3 @@ import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +-import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -22,2 +20,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,6 +28,12 @@ import { Root } from './components/Root'; + +-import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; ++import { ++ AlertDisplay, ++ OAuthRequestDialog, ++ SignInPage, ++} 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 +45,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -45,2 +52,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -50,10 +58,10 @@ const app = createApp({ + }, ++ components: { ++ SignInPage: props => , ++ }, + }); + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -65,5 +73,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -75,6 +85,9 @@ const routes = ( + } ++ path="/catalog-import" ++ element={ ++ ++ ++ ++ } + /> +- } /> + }> +@@ -87,4 +100,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -94,5 +107,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..853d175 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,22 +1,5 @@ +-/* +- * 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'; + import ExtensionIcon from '@material-ui/icons/Extension'; +-import MapIcon from '@material-ui/icons/MyLocation'; + import LibraryBooks from '@material-ui/icons/LibraryBooks'; +@@ -25,3 +8,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -30,6 +12,3 @@ import { + } from '@backstage/plugin-user-settings'; +-import { +- SidebarSearchModal, +- SearchContextProvider, +-} from '@backstage/plugin-search'; ++import { SidebarSearchModal } from '@backstage/plugin-search'; + import { +@@ -37,3 +16,2 @@ import { + sidebarConfig, +- SidebarContext, + SidebarDivider, +@@ -44,2 +22,4 @@ import { + SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -47,2 +27,4 @@ import MenuIcon from '@material-ui/icons/Menu'; + import SearchIcon from '@material-ui/icons/Search'; ++import { MyGroupsSidebarItem } from '@backstage/plugin-org'; ++import GroupIcon from '@material-ui/icons/People'; + +@@ -65,3 +47,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -69,8 +51,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -86,5 +63,3 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + } to="/search"> +- +- +- {' '} ++ + +@@ -94,2 +69,7 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + ++ + +@@ -100,3 +80,3 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ {/* Items in this group will be scrollable if they run out of space */} + +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..6164c76 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,7 +27,5 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +-import { +- isGithubActionsAvailable, +- EntityGithubActionsContent, +-} from '@backstage/plugin-github-actions'; + import { +@@ -70,2 +53,18 @@ import { + ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++import { ++ EntityKubernetesContent, ++ isKubernetesAvailable, ++} from '@backstage/plugin-kubernetes'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); ++ + const cicdContent = ( +@@ -74,5 +73,9 @@ const cicdContent = ( + +- +- +- ++ {/* ++ Here you can add support for different CI/CD services, for example ++ using @backstage-community/plugin-github-actions as follows: ++ ++ ++ ++ */} + +@@ -107,2 +110,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -146,2 +157,10 @@ const serviceEntityPage = ( + ++ ++ ++ ++ + +@@ -169,3 +188,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -184,2 +203,10 @@ const websiteEntityPage = ( + ++ ++ ++ ++ + +@@ -196,3 +223,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -215,3 +242,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -296,5 +323,8 @@ const groupPage = ( + +- ++ + + ++ ++ ++ + +@@ -315,3 +345,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/app/src/index.tsx b/packages/app/src/index.tsx +index b16aaf7..d875c77 100644 +--- a/packages/app/src/index.tsx ++++ b/packages/app/src/index.tsx +@@ -2,5 +2,5 @@ import '@backstage/cli/asset-types'; + import React from 'react'; +-import ReactDOM from 'react-dom'; ++import ReactDOM from 'react-dom/client'; + import App from './App'; + +-ReactDOM.render(, document.getElementById('root')); ++ReactDOM.createRoot(document.getElementById('root')!).render(); +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..bef67b5 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,15 @@ 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 ++ ++# This will include the examples, if you don't need these simply remove this line ++COPY --chown=node:node examples ./examples + + # 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..6a14abc 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,35 +6,43 @@ + "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", +- "dockerode": "^3.3.1", +- "express": "^4.17.1", +- "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "@backstage/backend-common": "^0.24.0", ++ "@backstage/backend-defaults": "^0.4.2", ++ "@backstage/backend-tasks": "^0.6.0", ++ "@backstage/config": "^1.2.0", ++ "@backstage/plugin-app-backend": "^0.3.72", ++ "@backstage/plugin-auth-backend": "^0.22.10", ++ "@backstage/plugin-auth-backend-module-github-provider": "^0.1.20", ++ "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.9", ++ "@backstage/plugin-auth-node": "^0.5.0", ++ "@backstage/plugin-catalog-backend": "^1.25.0", ++ "@backstage/plugin-catalog-backend-module-logs": "^0.0.2", ++ "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.21", ++ "@backstage/plugin-kubernetes-backend": "^0.18.4", ++ "@backstage/plugin-permission-backend": "^0.5.47", ++ "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.1.20", ++ "@backstage/plugin-permission-common": "^0.8.1", ++ "@backstage/plugin-permission-node": "^0.8.1", ++ "@backstage/plugin-proxy-backend": "^0.5.4", ++ "@backstage/plugin-scaffolder-backend": "^1.24.0", ++ "@backstage/plugin-search-backend": "^1.5.15", ++ "@backstage/plugin-search-backend-module-catalog": "^0.2.0", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.33", ++ "@backstage/plugin-search-backend-module-techdocs": "^0.2.0", ++ "@backstage/plugin-search-backend-node": "^1.3.0", ++ "@backstage/plugin-techdocs-backend": "^1.10.10", ++ "app": "link:../app", ++ "better-sqlite3": "^9.0.0", ++ "node-gyp": "^10.0.0", ++ "pg": "^8.11.3", + "winston": "^3.2.1" +@@ -42,6 +50,6 @@ + "devDependencies": { +- "@backstage/cli": "^0.13.0", +- "@types/dockerode": "^3.3.0", ++ "@backstage/cli": "^0.27.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.test.ts b/packages/backend/src/index.test.ts +deleted file mode 100644 +index 7814b8c..0000000 +--- a/packages/backend/src/index.test.ts ++++ /dev/null +@@ -1,8 +0,0 @@ +-import { PluginEnvironment } from './types'; +- +-describe('test', () => { +- it('unbreaks the test runner', () => { +- const unbreaker = {} as PluginEnvironment; +- expect(unbreaker).toBeTruthy(); +- }); +-}); +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 70bc66b..f57d4c5 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -8,102 +8,47 @@ + +-import Router from 'express-promise-router'; +-import { +- createServiceBuilder, +- loadBackendConfig, +- getRootLogger, +- useHotMemoize, +- notFoundHandler, +- CacheManager, +- DatabaseManager, +- SingleHostDiscovery, +- UrlReaders, +- ServerTokenManager, +-} from '@backstage/backend-common'; +-import { TaskScheduler } from '@backstage/backend-tasks'; +-import { Config } from '@backstage/config'; +-import app from './plugins/app'; +-import auth from './plugins/auth'; +-import catalog from './plugins/catalog'; +-import scaffolder from './plugins/scaffolder'; +-import proxy from './plugins/proxy'; +-import techdocs from './plugins/techdocs'; +-import search from './plugins/search'; +-import { PluginEnvironment } from './types'; +-import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { createBackend } from '@backstage/backend-defaults'; + +-function makeCreateEnv(config: Config) { +- const root = getRootLogger(); +- const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); +- const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); +- const permissions = ServerPermissionClient.fromConfig(config, { +- discovery, +- tokenManager, +- }); ++const backend = createBackend(); + +- root.info(`Created UrlReader ${reader}`); ++backend.add(import('@backstage/plugin-app-backend/alpha')); ++backend.add(import('@backstage/plugin-proxy-backend/alpha')); ++backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); ++backend.add(import('@backstage/plugin-techdocs-backend/alpha')); + +- return (plugin: string): PluginEnvironment => { +- const logger = root.child({ type: 'plugin', plugin }); +- const database = databaseManager.forPlugin(plugin); +- const cache = cacheManager.forPlugin(plugin); +- const scheduler = taskScheduler.forPlugin(plugin); +- return { +- logger, +- database, +- cache, +- config, +- reader, +- discovery, +- tokenManager, +- scheduler, +- permissions, +- }; +- }; +-} ++// auth plugin ++backend.add(import('@backstage/plugin-auth-backend')); ++// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin ++backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); ++// See https://backstage.io/docs/auth/guest/provider + +-async function main() { +- const config = await loadBackendConfig({ +- argv: process.argv, +- logger: getRootLogger(), +- }); +- const createEnv = makeCreateEnv(config); ++// catalog plugin ++backend.add(import('@backstage/plugin-catalog-backend/alpha')); ++backend.add( ++ import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ++); + +- const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); +- const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); +- const authEnv = useHotMemoize(module, () => createEnv('auth')); +- const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); +- const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); +- const searchEnv = useHotMemoize(module, () => createEnv('search')); +- const appEnv = useHotMemoize(module, () => createEnv('app')); ++// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors ++backend.add(import('@backstage/plugin-catalog-backend-module-logs')); + +- const apiRouter = Router(); +- apiRouter.use('/catalog', await catalog(catalogEnv)); +- apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); +- apiRouter.use('/auth', await auth(authEnv)); +- apiRouter.use('/techdocs', await techdocs(techdocsEnv)); +- apiRouter.use('/proxy', await proxy(proxyEnv)); +- apiRouter.use('/search', await search(searchEnv)); ++// permission plugin ++backend.add(import('@backstage/plugin-permission-backend/alpha')); ++// See https://backstage.io/docs/permissions/getting-started for how to create your own permission policy ++backend.add( ++ import('@backstage/plugin-permission-backend-module-allow-all-policy'), ++); + +- // Add backends ABOVE this line; this 404 handler is the catch-all fallback +- apiRouter.use(notFoundHandler()); ++// search plugin ++backend.add(import('@backstage/plugin-search-backend/alpha')); + +- const service = createServiceBuilder(module) +- .loadConfig(config) +- .addRouter('/api', apiRouter) +- .addRouter('', await app(appEnv)); ++// search engine ++// See https://backstage.io/docs/features/search/search-engines ++backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); + +- await service.start().catch(err => { +- console.log(err); +- process.exit(1); +- }); +-} ++// search collators ++backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); ++backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); + +-module.hot?.accept(); +-main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); +- process.exit(1); +-}); ++// kubernetes ++backend.add(import('@backstage/plugin-kubernetes-backend/alpha')); ++ ++backend.start(); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +deleted file mode 100644 +index 14e19a1..0000000 +--- a/packages/backend/src/plugins/app.ts ++++ /dev/null +@@ -1,16 +0,0 @@ +-import { createRouter } from '@backstage/plugin-app-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- database, +-}: PluginEnvironment): Promise { +- return await createRouter({ +- logger, +- config, +- database, +- appPackageName: 'app', +- }); +-} +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +deleted file mode 100644 +index 5216510..0000000 +--- a/packages/backend/src/plugins/auth.ts ++++ /dev/null +@@ -1,12 +0,0 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, database, discovery }); +-} +diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts +deleted file mode 100644 +index 876cb6b..0000000 +--- a/packages/backend/src/plugins/catalog.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +-import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const builder = await CatalogBuilder.create(env); +- builder.addProcessor(new ScaffolderEntitiesProcessor()); +- const { processingEngine, router } = await builder.build(); +- await processingEngine.start(); +- return router; +-} +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +deleted file mode 100644 +index 506f6d9..0000000 +--- a/packages/backend/src/plugins/proxy.ts ++++ /dev/null +@@ -1,11 +0,0 @@ +-import { createRouter } from '@backstage/plugin-proxy-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); +-} +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +deleted file mode 100644 +index 6be2e97..0000000 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ /dev/null +@@ -1,27 +0,0 @@ +-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'; +-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 }); +- +- return await createRouter({ +- containerRunner, +- logger, +- config, +- database, +- catalogClient, +- reader, +- }); +-} +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +deleted file mode 100644 +index a0a1cc3..0000000 +--- a/packages/backend/src/plugins/search.ts ++++ /dev/null +@@ -1,58 +0,0 @@ +-import { useHotCleanup } from '@backstage/backend-common'; +-import { createRouter } from '@backstage/plugin-search-backend'; +-import { +- IndexBuilder, +- LunrSearchEngine, +-} from '@backstage/plugin-search-backend-node'; +-import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend'; +- +-export default async function createPlugin({ +- logger, +- permissions, +- discovery, +- config, +- tokenManager, +-}: PluginEnvironment) { +- // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); +- +- // Collators are responsible for gathering documents known to plugins. This +- // collator gathers entities from the software catalog. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { +- discovery, +- tokenManager, +- }), +- }); +- +- // collator gathers entities from techdocs. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultTechDocsCollator.fromConfig(config, { +- discovery, +- logger, +- tokenManager, +- }), +- }); +- +- // The scheduler controls when documents are gathered from collators and sent +- // to the search engine for indexing. +- const { scheduler } = await indexBuilder.build(); +- +- // 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()); +- +- return await createRouter({ +- engine: indexBuilder.getSearchEngine(), +- types: indexBuilder.getDocumentTypes(), +- permissions, +- config, +- logger, +- }); +-} +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +deleted file mode 100644 +index 054c64d..0000000 +--- a/packages/backend/src/plugins/techdocs.ts ++++ /dev/null +@@ -1,55 +0,0 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; +-import { +- createRouter, +- Generators, +- Preparers, +- Publisher, +-} from '@backstage/plugin-techdocs-backend'; +-import Docker from 'dockerode'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +- cache, +-}: PluginEnvironment): Promise { +- // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, +- }); +- +- // Docker client (conditionally) used by the generators, based on techdocs.generators config. +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- +- // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, +- containerRunner, +- }); +- +- // Publisher is used for +- // 1. Publishing generated files to storage +- // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, +- }); +- +- // checks if the publisher is working and logs the result +- await publisher.getReadiness(); +- +- return await createRouter({ +- preparers, +- generators, +- publisher, +- logger, +- config, +- discovery, +- cache, +- }); +-} +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +deleted file mode 100644 +index 0862b0e..0000000 +--- a/packages/backend/src/types.ts ++++ /dev/null +@@ -1,23 +0,0 @@ +-import { Logger } from 'winston'; +-import { Config } from '@backstage/config'; +-import { +- PluginCacheManager, +- PluginDatabaseManager, +- PluginEndpointDiscovery, +- TokenManager, +- UrlReader, +-} from '@backstage/backend-common'; +-import { PluginTaskScheduler } from '@backstage/backend-tasks'; +-import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; +- +-export type PluginEnvironment = { +- logger: Logger; +- database: PluginDatabaseManager; +- cache: PluginCacheManager; +- config: Config; +- reader: UrlReader; +- discovery: PluginEndpointDiscovery; +- tokenManager: TokenManager; +- scheduler: PluginTaskScheduler; +- permissions: PermissionAuthorizer; +-}; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..733be13 +--- /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 dev', ++ 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..c37fc61 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1,3 @@ ++# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. ++# yarn lockfile v1 ++ diff --git a/diffs/0.4.18..0.5.18.diff b/diffs/0.4.18..0.5.18.diff new file mode 100644 index 00000000..88ed5284 --- /dev/null +++ b/diffs/0.4.18..0.5.18.diff @@ -0,0 +1,1944 @@ +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..5d426f5 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,51 @@ 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/apis/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 ++ ++auth: ++ providers: ++ guest: {} ++ ++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: ++ # Local example data, replace this with your production config, these are intended for demo use only. ++ # File locations are relative to the backend process, typically in a deployed context, such as in a Docker container, this will be the root ++ - type: file ++ target: ./examples/entities.yaml ++ ++ # Local example template ++ - type: file ++ target: ./examples/template/template.yaml ++ rules: ++ - allow: [Template] ++ ++ # Local example organizational data ++ - type: file ++ target: ./examples/org.yaml ++ rules: ++ - allow: [User, Group] +diff --git a/app-config.yaml b/app-config.yaml +index d45e354..ca52ec5 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/integrations/github/locations#configuration + 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 + +@@ -59,3 +66,5 @@ auth: + # see https://backstage.io/docs/auth/ to learn about auth providers +- providers: {} ++ providers: ++ # See https://backstage.io/docs/auth/guest/provider ++ guest: {} + +@@ -69,40 +78,36 @@ 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 ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml ++ rules: ++ - allow: [Template] + +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml ++ rules: ++ - allow: [User, Group] + +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml + +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.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] + +- # 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 +- 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 +- rules: +- - allow: [Template] ++kubernetes: ++ # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options ++ ++# see https://backstage.io/docs/permissions/getting-started for more on the permission framework ++permission: ++ # setting this to `false` will disable permissions ++ enabled: true +diff --git a/backstage.json b/backstage.json +index 542bc3e..230d39d 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "0.4.18" ++ "version": "1.30.0" + } +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..80a963e 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.27.0", ++ "@backstage/e2e-test-utils": "^0.1.1", ++ "@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": "^10.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.5.4" ++ "typescript": "~5.4.0" ++ }, ++ "resolutions": { ++ "@types/react": "^18", ++ "@types/react-dom": "^18" + }, +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..839ff88 +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,27 @@ ++/* ++ * 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('/'); ++ ++ const enterButton = page.getByRole('button', { name: 'Enter' }); ++ await expect(enterButton).toBeVisible(); ++ await enterButton.click(); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 3ed23fe..346ffc1 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,25 +5,37 @@ + "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.5.10", ++ "@backstage/catalog-model": "^1.6.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/core-app-api": "^1.14.2", ++ "@backstage/core-components": "^0.14.10", ++ "@backstage/core-plugin-api": "^1.9.3", ++ "@backstage/integration-react": "^1.1.30", ++ "@backstage/plugin-api-docs": "^0.11.8", ++ "@backstage/plugin-catalog": "^1.22.0", ++ "@backstage/plugin-catalog-common": "^1.0.26", ++ "@backstage/plugin-catalog-graph": "^0.4.8", ++ "@backstage/plugin-catalog-import": "^0.12.2", ++ "@backstage/plugin-catalog-react": "^1.12.3", ++ "@backstage/plugin-kubernetes": "^0.11.13", ++ "@backstage/plugin-org": "^0.6.28", ++ "@backstage/plugin-permission-react": "^0.4.25", ++ "@backstage/plugin-scaffolder": "^1.24.0", ++ "@backstage/plugin-search": "^1.4.15", ++ "@backstage/plugin-search-react": "^1.7.14", ++ "@backstage/plugin-techdocs": "^1.10.8", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.13", ++ "@backstage/plugin-techdocs-react": "^1.2.7", ++ "@backstage/plugin-user-settings": "^0.8.11", ++ "@backstage/theme": "^0.5.6", + "@material-ui/core": "^4.12.2", +@@ -31,31 +43,17 @@ + "history": "^5.0.0", +- "react": "^17.0.2", +- "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": "^18.0.2", ++ "react-dom": "^18.0.2", ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^0.2.4", +- "@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", ++ "@backstage/test-utils": "^1.5.10", ++ "@playwright/test": "^1.32.3", ++ "@testing-library/jest-dom": "^6.0.0", ++ "@testing-library/react": "^14.0.0", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^9.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..18da7c4 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -8,5 +8,4 @@ + name="description" +- content="Backstage is an open platform for building developer portals" ++ content="Backstage is an open source framework for building developer portals" + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index b94cac7..ec8ba1d 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { renderWithEffects } from '@backstage/test-utils'; ++import { render, waitFor } from '@testing-library/react'; + import App from './App'; +@@ -22,4 +22,7 @@ describe('App', () => { + +- const rendered = await renderWithEffects(); +- expect(rendered.baseElement).toBeInTheDocument(); ++ const rendered = render(); ++ ++ await waitFor(() => { ++ expect(rendered.baseElement).toBeInTheDocument(); ++ }); + }); +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 9b65186..e24c468 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'; +@@ -15,5 +15,3 @@ import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +-import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -22,2 +20,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,8 +28,12 @@ import { Root } from './components/Root'; + +-import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; ++import { ++ AlertDisplay, ++ OAuthRequestDialog, ++ SignInPage, ++} 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 +45,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -47,2 +52,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -52,10 +58,10 @@ const app = createApp({ + }, ++ components: { ++ SignInPage: props => , ++ }, + }); + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -67,5 +73,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -77,9 +85,8 @@ const routes = ( + } +- /> +- } ++ element={ ++ ++ ++ ++ } + /> +@@ -93,4 +100,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -100,5 +107,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..853d175 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,22 +1,5 @@ +-/* +- * 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'; + import ExtensionIcon from '@material-ui/icons/Extension'; +-import MapIcon from '@material-ui/icons/MyLocation'; + import LibraryBooks from '@material-ui/icons/LibraryBooks'; +@@ -25,3 +8,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -30,6 +12,3 @@ import { + } from '@backstage/plugin-user-settings'; +-import { +- SidebarSearchModal, +- SearchContextProvider, +-} from '@backstage/plugin-search'; ++import { SidebarSearchModal } from '@backstage/plugin-search'; + import { +@@ -37,3 +16,2 @@ import { + sidebarConfig, +- SidebarContext, + SidebarDivider, +@@ -44,2 +22,4 @@ import { + SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -47,2 +27,4 @@ import MenuIcon from '@material-ui/icons/Menu'; + import SearchIcon from '@material-ui/icons/Search'; ++import { MyGroupsSidebarItem } from '@backstage/plugin-org'; ++import GroupIcon from '@material-ui/icons/People'; + +@@ -65,3 +47,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -69,8 +51,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -86,5 +63,3 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + } to="/search"> +- +- +- {' '} ++ + +@@ -94,2 +69,7 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + ++ + +@@ -100,3 +80,3 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ {/* Items in this group will be scrollable if they run out of space */} + +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..6164c76 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,7 +27,5 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +-import { +- isGithubActionsAvailable, +- EntityGithubActionsContent, +-} from '@backstage/plugin-github-actions'; + import { +@@ -70,2 +53,18 @@ import { + ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++import { ++ EntityKubernetesContent, ++ isKubernetesAvailable, ++} from '@backstage/plugin-kubernetes'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); ++ + const cicdContent = ( +@@ -74,5 +73,9 @@ const cicdContent = ( + +- +- +- ++ {/* ++ Here you can add support for different CI/CD services, for example ++ using @backstage-community/plugin-github-actions as follows: ++ ++ ++ ++ */} + +@@ -107,2 +110,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -146,2 +157,10 @@ const serviceEntityPage = ( + ++ ++ ++ ++ + +@@ -169,3 +188,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -184,2 +203,10 @@ const websiteEntityPage = ( + ++ ++ ++ ++ + +@@ -196,3 +223,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -215,3 +242,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -296,5 +323,8 @@ const groupPage = ( + +- ++ + + ++ ++ ++ + +@@ -315,3 +345,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/app/src/index.tsx b/packages/app/src/index.tsx +index b16aaf7..d875c77 100644 +--- a/packages/app/src/index.tsx ++++ b/packages/app/src/index.tsx +@@ -2,5 +2,5 @@ import '@backstage/cli/asset-types'; + import React from 'react'; +-import ReactDOM from 'react-dom'; ++import ReactDOM from 'react-dom/client'; + import App from './App'; + +-ReactDOM.render(, document.getElementById('root')); ++ReactDOM.createRoot(document.getElementById('root')!).render(); +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..bef67b5 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,15 @@ 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 ++ ++# This will include the examples, if you don't need these simply remove this line ++COPY --chown=node:node examples ./examples + + # 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..6a14abc 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,35 +6,43 @@ + "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", +- "dockerode": "^3.3.1", +- "express": "^4.17.1", +- "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "@backstage/backend-common": "^0.24.0", ++ "@backstage/backend-defaults": "^0.4.2", ++ "@backstage/backend-tasks": "^0.6.0", ++ "@backstage/config": "^1.2.0", ++ "@backstage/plugin-app-backend": "^0.3.72", ++ "@backstage/plugin-auth-backend": "^0.22.10", ++ "@backstage/plugin-auth-backend-module-github-provider": "^0.1.20", ++ "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.9", ++ "@backstage/plugin-auth-node": "^0.5.0", ++ "@backstage/plugin-catalog-backend": "^1.25.0", ++ "@backstage/plugin-catalog-backend-module-logs": "^0.0.2", ++ "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.21", ++ "@backstage/plugin-kubernetes-backend": "^0.18.4", ++ "@backstage/plugin-permission-backend": "^0.5.47", ++ "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.1.20", ++ "@backstage/plugin-permission-common": "^0.8.1", ++ "@backstage/plugin-permission-node": "^0.8.1", ++ "@backstage/plugin-proxy-backend": "^0.5.4", ++ "@backstage/plugin-scaffolder-backend": "^1.24.0", ++ "@backstage/plugin-search-backend": "^1.5.15", ++ "@backstage/plugin-search-backend-module-catalog": "^0.2.0", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.33", ++ "@backstage/plugin-search-backend-module-techdocs": "^0.2.0", ++ "@backstage/plugin-search-backend-node": "^1.3.0", ++ "@backstage/plugin-techdocs-backend": "^1.10.10", ++ "app": "link:../app", ++ "better-sqlite3": "^9.0.0", ++ "node-gyp": "^10.0.0", ++ "pg": "^8.11.3", + "winston": "^3.2.1" +@@ -42,6 +50,6 @@ + "devDependencies": { +- "@backstage/cli": "^0.13.1", +- "@types/dockerode": "^3.3.0", ++ "@backstage/cli": "^0.27.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.test.ts b/packages/backend/src/index.test.ts +deleted file mode 100644 +index 7814b8c..0000000 +--- a/packages/backend/src/index.test.ts ++++ /dev/null +@@ -1,8 +0,0 @@ +-import { PluginEnvironment } from './types'; +- +-describe('test', () => { +- it('unbreaks the test runner', () => { +- const unbreaker = {} as PluginEnvironment; +- expect(unbreaker).toBeTruthy(); +- }); +-}); +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 70bc66b..f57d4c5 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -8,102 +8,47 @@ + +-import Router from 'express-promise-router'; +-import { +- createServiceBuilder, +- loadBackendConfig, +- getRootLogger, +- useHotMemoize, +- notFoundHandler, +- CacheManager, +- DatabaseManager, +- SingleHostDiscovery, +- UrlReaders, +- ServerTokenManager, +-} from '@backstage/backend-common'; +-import { TaskScheduler } from '@backstage/backend-tasks'; +-import { Config } from '@backstage/config'; +-import app from './plugins/app'; +-import auth from './plugins/auth'; +-import catalog from './plugins/catalog'; +-import scaffolder from './plugins/scaffolder'; +-import proxy from './plugins/proxy'; +-import techdocs from './plugins/techdocs'; +-import search from './plugins/search'; +-import { PluginEnvironment } from './types'; +-import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { createBackend } from '@backstage/backend-defaults'; + +-function makeCreateEnv(config: Config) { +- const root = getRootLogger(); +- const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); +- const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); +- const permissions = ServerPermissionClient.fromConfig(config, { +- discovery, +- tokenManager, +- }); ++const backend = createBackend(); + +- root.info(`Created UrlReader ${reader}`); ++backend.add(import('@backstage/plugin-app-backend/alpha')); ++backend.add(import('@backstage/plugin-proxy-backend/alpha')); ++backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); ++backend.add(import('@backstage/plugin-techdocs-backend/alpha')); + +- return (plugin: string): PluginEnvironment => { +- const logger = root.child({ type: 'plugin', plugin }); +- const database = databaseManager.forPlugin(plugin); +- const cache = cacheManager.forPlugin(plugin); +- const scheduler = taskScheduler.forPlugin(plugin); +- return { +- logger, +- database, +- cache, +- config, +- reader, +- discovery, +- tokenManager, +- scheduler, +- permissions, +- }; +- }; +-} ++// auth plugin ++backend.add(import('@backstage/plugin-auth-backend')); ++// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin ++backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); ++// See https://backstage.io/docs/auth/guest/provider + +-async function main() { +- const config = await loadBackendConfig({ +- argv: process.argv, +- logger: getRootLogger(), +- }); +- const createEnv = makeCreateEnv(config); ++// catalog plugin ++backend.add(import('@backstage/plugin-catalog-backend/alpha')); ++backend.add( ++ import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ++); + +- const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); +- const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); +- const authEnv = useHotMemoize(module, () => createEnv('auth')); +- const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); +- const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); +- const searchEnv = useHotMemoize(module, () => createEnv('search')); +- const appEnv = useHotMemoize(module, () => createEnv('app')); ++// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors ++backend.add(import('@backstage/plugin-catalog-backend-module-logs')); + +- const apiRouter = Router(); +- apiRouter.use('/catalog', await catalog(catalogEnv)); +- apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); +- apiRouter.use('/auth', await auth(authEnv)); +- apiRouter.use('/techdocs', await techdocs(techdocsEnv)); +- apiRouter.use('/proxy', await proxy(proxyEnv)); +- apiRouter.use('/search', await search(searchEnv)); ++// permission plugin ++backend.add(import('@backstage/plugin-permission-backend/alpha')); ++// See https://backstage.io/docs/permissions/getting-started for how to create your own permission policy ++backend.add( ++ import('@backstage/plugin-permission-backend-module-allow-all-policy'), ++); + +- // Add backends ABOVE this line; this 404 handler is the catch-all fallback +- apiRouter.use(notFoundHandler()); ++// search plugin ++backend.add(import('@backstage/plugin-search-backend/alpha')); + +- const service = createServiceBuilder(module) +- .loadConfig(config) +- .addRouter('/api', apiRouter) +- .addRouter('', await app(appEnv)); ++// search engine ++// See https://backstage.io/docs/features/search/search-engines ++backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); + +- await service.start().catch(err => { +- console.log(err); +- process.exit(1); +- }); +-} ++// search collators ++backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); ++backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); + +-module.hot?.accept(); +-main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); +- process.exit(1); +-}); ++// kubernetes ++backend.add(import('@backstage/plugin-kubernetes-backend/alpha')); ++ ++backend.start(); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +deleted file mode 100644 +index 14e19a1..0000000 +--- a/packages/backend/src/plugins/app.ts ++++ /dev/null +@@ -1,16 +0,0 @@ +-import { createRouter } from '@backstage/plugin-app-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- database, +-}: PluginEnvironment): Promise { +- return await createRouter({ +- logger, +- config, +- database, +- appPackageName: 'app', +- }); +-} +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +deleted file mode 100644 +index 015c864..0000000 +--- a/packages/backend/src/plugins/auth.ts ++++ /dev/null +@@ -1,19 +0,0 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +- tokenManager, +-}: PluginEnvironment): Promise { +- return await createRouter({ +- logger, +- config, +- database, +- discovery, +- tokenManager, +- }); +-} +diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts +deleted file mode 100644 +index 876cb6b..0000000 +--- a/packages/backend/src/plugins/catalog.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +-import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const builder = await CatalogBuilder.create(env); +- builder.addProcessor(new ScaffolderEntitiesProcessor()); +- const { processingEngine, router } = await builder.build(); +- await processingEngine.start(); +- return router; +-} +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +deleted file mode 100644 +index 506f6d9..0000000 +--- a/packages/backend/src/plugins/proxy.ts ++++ /dev/null +@@ -1,11 +0,0 @@ +-import { createRouter } from '@backstage/plugin-proxy-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); +-} +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +deleted file mode 100644 +index 6be2e97..0000000 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ /dev/null +@@ -1,27 +0,0 @@ +-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'; +-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 }); +- +- return await createRouter({ +- containerRunner, +- logger, +- config, +- database, +- catalogClient, +- reader, +- }); +-} +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +deleted file mode 100644 +index a0a1cc3..0000000 +--- a/packages/backend/src/plugins/search.ts ++++ /dev/null +@@ -1,58 +0,0 @@ +-import { useHotCleanup } from '@backstage/backend-common'; +-import { createRouter } from '@backstage/plugin-search-backend'; +-import { +- IndexBuilder, +- LunrSearchEngine, +-} from '@backstage/plugin-search-backend-node'; +-import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend'; +- +-export default async function createPlugin({ +- logger, +- permissions, +- discovery, +- config, +- tokenManager, +-}: PluginEnvironment) { +- // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); +- +- // Collators are responsible for gathering documents known to plugins. This +- // collator gathers entities from the software catalog. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { +- discovery, +- tokenManager, +- }), +- }); +- +- // collator gathers entities from techdocs. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultTechDocsCollator.fromConfig(config, { +- discovery, +- logger, +- tokenManager, +- }), +- }); +- +- // The scheduler controls when documents are gathered from collators and sent +- // to the search engine for indexing. +- const { scheduler } = await indexBuilder.build(); +- +- // 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()); +- +- return await createRouter({ +- engine: indexBuilder.getSearchEngine(), +- types: indexBuilder.getDocumentTypes(), +- permissions, +- config, +- logger, +- }); +-} +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +deleted file mode 100644 +index 054c64d..0000000 +--- a/packages/backend/src/plugins/techdocs.ts ++++ /dev/null +@@ -1,55 +0,0 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; +-import { +- createRouter, +- Generators, +- Preparers, +- Publisher, +-} from '@backstage/plugin-techdocs-backend'; +-import Docker from 'dockerode'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +- cache, +-}: PluginEnvironment): Promise { +- // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, +- }); +- +- // Docker client (conditionally) used by the generators, based on techdocs.generators config. +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- +- // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, +- containerRunner, +- }); +- +- // Publisher is used for +- // 1. Publishing generated files to storage +- // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, +- }); +- +- // checks if the publisher is working and logs the result +- await publisher.getReadiness(); +- +- return await createRouter({ +- preparers, +- generators, +- publisher, +- logger, +- config, +- discovery, +- cache, +- }); +-} +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +deleted file mode 100644 +index 0862b0e..0000000 +--- a/packages/backend/src/types.ts ++++ /dev/null +@@ -1,23 +0,0 @@ +-import { Logger } from 'winston'; +-import { Config } from '@backstage/config'; +-import { +- PluginCacheManager, +- PluginDatabaseManager, +- PluginEndpointDiscovery, +- TokenManager, +- UrlReader, +-} from '@backstage/backend-common'; +-import { PluginTaskScheduler } from '@backstage/backend-tasks'; +-import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; +- +-export type PluginEnvironment = { +- logger: Logger; +- database: PluginDatabaseManager; +- cache: PluginCacheManager; +- config: Config; +- reader: UrlReader; +- discovery: PluginEndpointDiscovery; +- tokenManager: TokenManager; +- scheduler: PluginTaskScheduler; +- permissions: PermissionAuthorizer; +-}; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..733be13 +--- /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 dev', ++ 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..c37fc61 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1,3 @@ ++# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. ++# yarn lockfile v1 ++ diff --git a/diffs/0.4.19..0.5.18.diff b/diffs/0.4.19..0.5.18.diff new file mode 100644 index 00000000..cb520232 --- /dev/null +++ b/diffs/0.4.19..0.5.18.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 5e36c23..5d426f5 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,51 @@ 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/apis/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 ++ ++auth: ++ providers: ++ guest: {} ++ ++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: ++ # Local example data, replace this with your production config, these are intended for demo use only. ++ # File locations are relative to the backend process, typically in a deployed context, such as in a Docker container, this will be the root ++ - type: file ++ target: ./examples/entities.yaml ++ ++ # Local example template ++ - type: file ++ target: ./examples/template/template.yaml ++ rules: ++ - allow: [Template] ++ ++ # Local example organizational data ++ - type: file ++ target: ./examples/org.yaml ++ rules: ++ - allow: [User, Group] +diff --git a/app-config.yaml b/app-config.yaml +index d45e354..ca52ec5 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/integrations/github/locations#configuration + 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 + +@@ -59,3 +66,5 @@ auth: + # see https://backstage.io/docs/auth/ to learn about auth providers +- providers: {} ++ providers: ++ # See https://backstage.io/docs/auth/guest/provider ++ guest: {} + +@@ -69,40 +78,36 @@ 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 ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml ++ rules: ++ - allow: [Template] + +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml ++ rules: ++ - allow: [User, Group] + +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml + +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.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] + +- # 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 +- 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 +- rules: +- - allow: [Template] ++kubernetes: ++ # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options ++ ++# see https://backstage.io/docs/permissions/getting-started for more on the permission framework ++permission: ++ # setting this to `false` will disable permissions ++ enabled: true +diff --git a/backstage.json b/backstage.json +index 5d45824..230d39d 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "0.4.19" ++ "version": "1.30.0" + } +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..80a963e 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.27.0", ++ "@backstage/e2e-test-utils": "^0.1.1", ++ "@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": "^10.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.5.4" ++ "typescript": "~5.4.0" ++ }, ++ "resolutions": { ++ "@types/react": "^18", ++ "@types/react-dom": "^18" + }, +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..839ff88 +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,27 @@ ++/* ++ * 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('/'); ++ ++ const enterButton = page.getByRole('button', { name: 'Enter' }); ++ await expect(enterButton).toBeVisible(); ++ await enterButton.click(); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 447761d..346ffc1 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,25 +5,37 @@ + "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.5.10", ++ "@backstage/catalog-model": "^1.6.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/core-app-api": "^1.14.2", ++ "@backstage/core-components": "^0.14.10", ++ "@backstage/core-plugin-api": "^1.9.3", ++ "@backstage/integration-react": "^1.1.30", ++ "@backstage/plugin-api-docs": "^0.11.8", ++ "@backstage/plugin-catalog": "^1.22.0", ++ "@backstage/plugin-catalog-common": "^1.0.26", ++ "@backstage/plugin-catalog-graph": "^0.4.8", ++ "@backstage/plugin-catalog-import": "^0.12.2", ++ "@backstage/plugin-catalog-react": "^1.12.3", ++ "@backstage/plugin-kubernetes": "^0.11.13", ++ "@backstage/plugin-org": "^0.6.28", ++ "@backstage/plugin-permission-react": "^0.4.25", ++ "@backstage/plugin-scaffolder": "^1.24.0", ++ "@backstage/plugin-search": "^1.4.15", ++ "@backstage/plugin-search-react": "^1.7.14", ++ "@backstage/plugin-techdocs": "^1.10.8", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.13", ++ "@backstage/plugin-techdocs-react": "^1.2.7", ++ "@backstage/plugin-user-settings": "^0.8.11", ++ "@backstage/theme": "^0.5.6", + "@material-ui/core": "^4.12.2", +@@ -31,31 +43,17 @@ + "history": "^5.0.0", +- "react": "^17.0.2", +- "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": "^18.0.2", ++ "react-dom": "^18.0.2", ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^0.2.4", +- "@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", ++ "@backstage/test-utils": "^1.5.10", ++ "@playwright/test": "^1.32.3", ++ "@testing-library/jest-dom": "^6.0.0", ++ "@testing-library/react": "^14.0.0", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^9.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..18da7c4 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -8,5 +8,4 @@ + name="description" +- content="Backstage is an open platform for building developer portals" ++ content="Backstage is an open source framework for building developer portals" + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index b94cac7..ec8ba1d 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { renderWithEffects } from '@backstage/test-utils'; ++import { render, waitFor } from '@testing-library/react'; + import App from './App'; +@@ -22,4 +22,7 @@ describe('App', () => { + +- const rendered = await renderWithEffects(); +- expect(rendered.baseElement).toBeInTheDocument(); ++ const rendered = render(); ++ ++ await waitFor(() => { ++ expect(rendered.baseElement).toBeInTheDocument(); ++ }); + }); +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 9b65186..e24c468 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'; +@@ -15,5 +15,3 @@ import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +-import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -22,2 +20,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,8 +28,12 @@ import { Root } from './components/Root'; + +-import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; ++import { ++ AlertDisplay, ++ OAuthRequestDialog, ++ SignInPage, ++} 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 +45,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -47,2 +52,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -52,10 +58,10 @@ const app = createApp({ + }, ++ components: { ++ SignInPage: props => , ++ }, + }); + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -67,5 +73,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -77,9 +85,8 @@ const routes = ( + } +- /> +- } ++ element={ ++ ++ ++ ++ } + /> +@@ -93,4 +100,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -100,5 +107,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..853d175 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,22 +1,5 @@ +-/* +- * 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'; + import ExtensionIcon from '@material-ui/icons/Extension'; +-import MapIcon from '@material-ui/icons/MyLocation'; + import LibraryBooks from '@material-ui/icons/LibraryBooks'; +@@ -25,3 +8,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -30,6 +12,3 @@ import { + } from '@backstage/plugin-user-settings'; +-import { +- SidebarSearchModal, +- SearchContextProvider, +-} from '@backstage/plugin-search'; ++import { SidebarSearchModal } from '@backstage/plugin-search'; + import { +@@ -37,3 +16,2 @@ import { + sidebarConfig, +- SidebarContext, + SidebarDivider, +@@ -44,2 +22,4 @@ import { + SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -47,2 +27,4 @@ import MenuIcon from '@material-ui/icons/Menu'; + import SearchIcon from '@material-ui/icons/Search'; ++import { MyGroupsSidebarItem } from '@backstage/plugin-org'; ++import GroupIcon from '@material-ui/icons/People'; + +@@ -65,3 +47,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -69,8 +51,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -86,5 +63,3 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + } to="/search"> +- +- +- {' '} ++ + +@@ -94,2 +69,7 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + ++ + +@@ -100,3 +80,3 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ {/* Items in this group will be scrollable if they run out of space */} + +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..6164c76 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,7 +27,5 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +-import { +- isGithubActionsAvailable, +- EntityGithubActionsContent, +-} from '@backstage/plugin-github-actions'; + import { +@@ -70,2 +53,18 @@ import { + ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++import { ++ EntityKubernetesContent, ++ isKubernetesAvailable, ++} from '@backstage/plugin-kubernetes'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); ++ + const cicdContent = ( +@@ -74,5 +73,9 @@ const cicdContent = ( + +- +- +- ++ {/* ++ Here you can add support for different CI/CD services, for example ++ using @backstage-community/plugin-github-actions as follows: ++ ++ ++ ++ */} + +@@ -107,2 +110,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -146,2 +157,10 @@ const serviceEntityPage = ( + ++ ++ ++ ++ + +@@ -169,3 +188,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -184,2 +203,10 @@ const websiteEntityPage = ( + ++ ++ ++ ++ + +@@ -196,3 +223,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -215,3 +242,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -296,5 +323,8 @@ const groupPage = ( + +- ++ + + ++ ++ ++ + +@@ -315,3 +345,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/app/src/index.tsx b/packages/app/src/index.tsx +index b16aaf7..d875c77 100644 +--- a/packages/app/src/index.tsx ++++ b/packages/app/src/index.tsx +@@ -2,5 +2,5 @@ import '@backstage/cli/asset-types'; + import React from 'react'; +-import ReactDOM from 'react-dom'; ++import ReactDOM from 'react-dom/client'; + import App from './App'; + +-ReactDOM.render(, document.getElementById('root')); ++ReactDOM.createRoot(document.getElementById('root')!).render(); +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..bef67b5 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,15 @@ 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 ++ ++# This will include the examples, if you don't need these simply remove this line ++COPY --chown=node:node examples ./examples + + # 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..6a14abc 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,35 +6,43 @@ + "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.24.0", ++ "@backstage/backend-defaults": "^0.4.2", ++ "@backstage/backend-tasks": "^0.6.0", ++ "@backstage/config": "^1.2.0", ++ "@backstage/plugin-app-backend": "^0.3.72", ++ "@backstage/plugin-auth-backend": "^0.22.10", ++ "@backstage/plugin-auth-backend-module-github-provider": "^0.1.20", ++ "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.9", ++ "@backstage/plugin-auth-node": "^0.5.0", ++ "@backstage/plugin-catalog-backend": "^1.25.0", ++ "@backstage/plugin-catalog-backend-module-logs": "^0.0.2", ++ "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.21", ++ "@backstage/plugin-kubernetes-backend": "^0.18.4", ++ "@backstage/plugin-permission-backend": "^0.5.47", ++ "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.1.20", ++ "@backstage/plugin-permission-common": "^0.8.1", ++ "@backstage/plugin-permission-node": "^0.8.1", ++ "@backstage/plugin-proxy-backend": "^0.5.4", ++ "@backstage/plugin-scaffolder-backend": "^1.24.0", ++ "@backstage/plugin-search-backend": "^1.5.15", ++ "@backstage/plugin-search-backend-module-catalog": "^0.2.0", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.33", ++ "@backstage/plugin-search-backend-module-techdocs": "^0.2.0", ++ "@backstage/plugin-search-backend-node": "^1.3.0", ++ "@backstage/plugin-techdocs-backend": "^1.10.10", + "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", +- "dockerode": "^3.3.1", +- "express": "^4.17.1", +- "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "@vscode/sqlite3": "^5.0.7", ++ "better-sqlite3": "^9.0.0", ++ "node-gyp": "^10.0.0", ++ "pg": "^8.11.3", + "winston": "^3.2.1" +@@ -42,6 +50,6 @@ + "devDependencies": { +- "@backstage/cli": "^0.13.2", +- "@types/dockerode": "^3.3.0", ++ "@backstage/cli": "^0.27.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.test.ts b/packages/backend/src/index.test.ts +deleted file mode 100644 +index 7814b8c..0000000 +--- a/packages/backend/src/index.test.ts ++++ /dev/null +@@ -1,8 +0,0 @@ +-import { PluginEnvironment } from './types'; +- +-describe('test', () => { +- it('unbreaks the test runner', () => { +- const unbreaker = {} as PluginEnvironment; +- expect(unbreaker).toBeTruthy(); +- }); +-}); +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 70bc66b..f57d4c5 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -8,102 +8,47 @@ + +-import Router from 'express-promise-router'; +-import { +- createServiceBuilder, +- loadBackendConfig, +- getRootLogger, +- useHotMemoize, +- notFoundHandler, +- CacheManager, +- DatabaseManager, +- SingleHostDiscovery, +- UrlReaders, +- ServerTokenManager, +-} from '@backstage/backend-common'; +-import { TaskScheduler } from '@backstage/backend-tasks'; +-import { Config } from '@backstage/config'; +-import app from './plugins/app'; +-import auth from './plugins/auth'; +-import catalog from './plugins/catalog'; +-import scaffolder from './plugins/scaffolder'; +-import proxy from './plugins/proxy'; +-import techdocs from './plugins/techdocs'; +-import search from './plugins/search'; +-import { PluginEnvironment } from './types'; +-import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { createBackend } from '@backstage/backend-defaults'; + +-function makeCreateEnv(config: Config) { +- const root = getRootLogger(); +- const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); +- const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); +- const permissions = ServerPermissionClient.fromConfig(config, { +- discovery, +- tokenManager, +- }); ++const backend = createBackend(); + +- root.info(`Created UrlReader ${reader}`); ++backend.add(import('@backstage/plugin-app-backend/alpha')); ++backend.add(import('@backstage/plugin-proxy-backend/alpha')); ++backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); ++backend.add(import('@backstage/plugin-techdocs-backend/alpha')); + +- return (plugin: string): PluginEnvironment => { +- const logger = root.child({ type: 'plugin', plugin }); +- const database = databaseManager.forPlugin(plugin); +- const cache = cacheManager.forPlugin(plugin); +- const scheduler = taskScheduler.forPlugin(plugin); +- return { +- logger, +- database, +- cache, +- config, +- reader, +- discovery, +- tokenManager, +- scheduler, +- permissions, +- }; +- }; +-} ++// auth plugin ++backend.add(import('@backstage/plugin-auth-backend')); ++// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin ++backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); ++// See https://backstage.io/docs/auth/guest/provider + +-async function main() { +- const config = await loadBackendConfig({ +- argv: process.argv, +- logger: getRootLogger(), +- }); +- const createEnv = makeCreateEnv(config); ++// catalog plugin ++backend.add(import('@backstage/plugin-catalog-backend/alpha')); ++backend.add( ++ import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ++); + +- const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); +- const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); +- const authEnv = useHotMemoize(module, () => createEnv('auth')); +- const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); +- const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); +- const searchEnv = useHotMemoize(module, () => createEnv('search')); +- const appEnv = useHotMemoize(module, () => createEnv('app')); ++// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors ++backend.add(import('@backstage/plugin-catalog-backend-module-logs')); + +- const apiRouter = Router(); +- apiRouter.use('/catalog', await catalog(catalogEnv)); +- apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); +- apiRouter.use('/auth', await auth(authEnv)); +- apiRouter.use('/techdocs', await techdocs(techdocsEnv)); +- apiRouter.use('/proxy', await proxy(proxyEnv)); +- apiRouter.use('/search', await search(searchEnv)); ++// permission plugin ++backend.add(import('@backstage/plugin-permission-backend/alpha')); ++// See https://backstage.io/docs/permissions/getting-started for how to create your own permission policy ++backend.add( ++ import('@backstage/plugin-permission-backend-module-allow-all-policy'), ++); + +- // Add backends ABOVE this line; this 404 handler is the catch-all fallback +- apiRouter.use(notFoundHandler()); ++// search plugin ++backend.add(import('@backstage/plugin-search-backend/alpha')); + +- const service = createServiceBuilder(module) +- .loadConfig(config) +- .addRouter('/api', apiRouter) +- .addRouter('', await app(appEnv)); ++// search engine ++// See https://backstage.io/docs/features/search/search-engines ++backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); + +- await service.start().catch(err => { +- console.log(err); +- process.exit(1); +- }); +-} ++// search collators ++backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); ++backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); + +-module.hot?.accept(); +-main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); +- process.exit(1); +-}); ++// kubernetes ++backend.add(import('@backstage/plugin-kubernetes-backend/alpha')); ++ ++backend.start(); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +deleted file mode 100644 +index 14e19a1..0000000 +--- a/packages/backend/src/plugins/app.ts ++++ /dev/null +@@ -1,16 +0,0 @@ +-import { createRouter } from '@backstage/plugin-app-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- database, +-}: PluginEnvironment): Promise { +- return await createRouter({ +- logger, +- config, +- database, +- appPackageName: 'app', +- }); +-} +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +deleted file mode 100644 +index 015c864..0000000 +--- a/packages/backend/src/plugins/auth.ts ++++ /dev/null +@@ -1,19 +0,0 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +- tokenManager, +-}: PluginEnvironment): Promise { +- return await createRouter({ +- logger, +- config, +- database, +- discovery, +- tokenManager, +- }); +-} +diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts +deleted file mode 100644 +index 876cb6b..0000000 +--- a/packages/backend/src/plugins/catalog.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +-import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const builder = await CatalogBuilder.create(env); +- builder.addProcessor(new ScaffolderEntitiesProcessor()); +- const { processingEngine, router } = await builder.build(); +- await processingEngine.start(); +- return router; +-} +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +deleted file mode 100644 +index 506f6d9..0000000 +--- a/packages/backend/src/plugins/proxy.ts ++++ /dev/null +@@ -1,11 +0,0 @@ +-import { createRouter } from '@backstage/plugin-proxy-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); +-} +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +deleted file mode 100644 +index 6be2e97..0000000 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ /dev/null +@@ -1,27 +0,0 @@ +-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'; +-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 }); +- +- return await createRouter({ +- containerRunner, +- logger, +- config, +- database, +- catalogClient, +- reader, +- }); +-} +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +deleted file mode 100644 +index a0a1cc3..0000000 +--- a/packages/backend/src/plugins/search.ts ++++ /dev/null +@@ -1,58 +0,0 @@ +-import { useHotCleanup } from '@backstage/backend-common'; +-import { createRouter } from '@backstage/plugin-search-backend'; +-import { +- IndexBuilder, +- LunrSearchEngine, +-} from '@backstage/plugin-search-backend-node'; +-import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend'; +- +-export default async function createPlugin({ +- logger, +- permissions, +- discovery, +- config, +- tokenManager, +-}: PluginEnvironment) { +- // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); +- +- // Collators are responsible for gathering documents known to plugins. This +- // collator gathers entities from the software catalog. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { +- discovery, +- tokenManager, +- }), +- }); +- +- // collator gathers entities from techdocs. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultTechDocsCollator.fromConfig(config, { +- discovery, +- logger, +- tokenManager, +- }), +- }); +- +- // The scheduler controls when documents are gathered from collators and sent +- // to the search engine for indexing. +- const { scheduler } = await indexBuilder.build(); +- +- // 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()); +- +- return await createRouter({ +- engine: indexBuilder.getSearchEngine(), +- types: indexBuilder.getDocumentTypes(), +- permissions, +- config, +- logger, +- }); +-} +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +deleted file mode 100644 +index 054c64d..0000000 +--- a/packages/backend/src/plugins/techdocs.ts ++++ /dev/null +@@ -1,55 +0,0 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; +-import { +- createRouter, +- Generators, +- Preparers, +- Publisher, +-} from '@backstage/plugin-techdocs-backend'; +-import Docker from 'dockerode'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +- cache, +-}: PluginEnvironment): Promise { +- // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, +- }); +- +- // Docker client (conditionally) used by the generators, based on techdocs.generators config. +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- +- // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, +- containerRunner, +- }); +- +- // Publisher is used for +- // 1. Publishing generated files to storage +- // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, +- }); +- +- // checks if the publisher is working and logs the result +- await publisher.getReadiness(); +- +- return await createRouter({ +- preparers, +- generators, +- publisher, +- logger, +- config, +- discovery, +- cache, +- }); +-} +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +deleted file mode 100644 +index 0862b0e..0000000 +--- a/packages/backend/src/types.ts ++++ /dev/null +@@ -1,23 +0,0 @@ +-import { Logger } from 'winston'; +-import { Config } from '@backstage/config'; +-import { +- PluginCacheManager, +- PluginDatabaseManager, +- PluginEndpointDiscovery, +- TokenManager, +- UrlReader, +-} from '@backstage/backend-common'; +-import { PluginTaskScheduler } from '@backstage/backend-tasks'; +-import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; +- +-export type PluginEnvironment = { +- logger: Logger; +- database: PluginDatabaseManager; +- cache: PluginCacheManager; +- config: Config; +- reader: UrlReader; +- discovery: PluginEndpointDiscovery; +- tokenManager: TokenManager; +- scheduler: PluginTaskScheduler; +- permissions: PermissionAuthorizer; +-}; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..733be13 +--- /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 dev', ++ 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..c37fc61 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1,3 @@ ++# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. ++# yarn lockfile v1 ++ diff --git a/diffs/0.4.20..0.5.18.diff b/diffs/0.4.20..0.5.18.diff new file mode 100644 index 00000000..36e8d5f9 --- /dev/null +++ b/diffs/0.4.20..0.5.18.diff @@ -0,0 +1,1928 @@ +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..5d426f5 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,51 @@ 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/apis/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 ++ ++auth: ++ providers: ++ guest: {} ++ ++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: ++ # Local example data, replace this with your production config, these are intended for demo use only. ++ # File locations are relative to the backend process, typically in a deployed context, such as in a Docker container, this will be the root ++ - type: file ++ target: ./examples/entities.yaml ++ ++ # Local example template ++ - type: file ++ target: ./examples/template/template.yaml ++ rules: ++ - allow: [Template] ++ ++ # Local example organizational data ++ - type: file ++ target: ./examples/org.yaml ++ rules: ++ - allow: [User, Group] +diff --git a/app-config.yaml b/app-config.yaml +index d45e354..ca52ec5 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/integrations/github/locations#configuration + 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 + +@@ -59,3 +66,5 @@ auth: + # see https://backstage.io/docs/auth/ to learn about auth providers +- providers: {} ++ providers: ++ # See https://backstage.io/docs/auth/guest/provider ++ guest: {} + +@@ -69,40 +78,36 @@ 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 ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml ++ rules: ++ - allow: [Template] + +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml ++ rules: ++ - allow: [User, Group] + +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml + +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.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] + +- # 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 +- 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 +- rules: +- - allow: [Template] ++kubernetes: ++ # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options ++ ++# see https://backstage.io/docs/permissions/getting-started for more on the permission framework ++permission: ++ # setting this to `false` will disable permissions ++ enabled: true +diff --git a/backstage.json b/backstage.json +index 06a76a3..230d39d 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "0.4.20" ++ "version": "1.30.0" + } +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..80a963e 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.27.0", ++ "@backstage/e2e-test-utils": "^0.1.1", ++ "@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": "^10.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.5.4" ++ "typescript": "~5.4.0" ++ }, ++ "resolutions": { ++ "@types/react": "^18", ++ "@types/react-dom": "^18" + }, +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..839ff88 +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,27 @@ ++/* ++ * 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('/'); ++ ++ const enterButton = page.getByRole('button', { name: 'Enter' }); ++ await expect(enterButton).toBeVisible(); ++ await enterButton.click(); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 6022dc0..346ffc1 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,25 +5,37 @@ + "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.5.10", ++ "@backstage/catalog-model": "^1.6.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/core-app-api": "^1.14.2", ++ "@backstage/core-components": "^0.14.10", ++ "@backstage/core-plugin-api": "^1.9.3", ++ "@backstage/integration-react": "^1.1.30", ++ "@backstage/plugin-api-docs": "^0.11.8", ++ "@backstage/plugin-catalog": "^1.22.0", ++ "@backstage/plugin-catalog-common": "^1.0.26", ++ "@backstage/plugin-catalog-graph": "^0.4.8", ++ "@backstage/plugin-catalog-import": "^0.12.2", ++ "@backstage/plugin-catalog-react": "^1.12.3", ++ "@backstage/plugin-kubernetes": "^0.11.13", ++ "@backstage/plugin-org": "^0.6.28", ++ "@backstage/plugin-permission-react": "^0.4.25", ++ "@backstage/plugin-scaffolder": "^1.24.0", ++ "@backstage/plugin-search": "^1.4.15", ++ "@backstage/plugin-search-react": "^1.7.14", ++ "@backstage/plugin-techdocs": "^1.10.8", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.13", ++ "@backstage/plugin-techdocs-react": "^1.2.7", ++ "@backstage/plugin-user-settings": "^0.8.11", ++ "@backstage/theme": "^0.5.6", + "@material-ui/core": "^4.12.2", +@@ -31,31 +43,17 @@ + "history": "^5.0.0", +- "react": "^17.0.2", +- "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": "^18.0.2", ++ "react-dom": "^18.0.2", ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^0.2.5", +- "@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", ++ "@backstage/test-utils": "^1.5.10", ++ "@playwright/test": "^1.32.3", ++ "@testing-library/jest-dom": "^6.0.0", ++ "@testing-library/react": "^14.0.0", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^9.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..18da7c4 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -8,5 +8,4 @@ + name="description" +- content="Backstage is an open platform for building developer portals" ++ content="Backstage is an open source framework for building developer portals" + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index b94cac7..ec8ba1d 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { renderWithEffects } from '@backstage/test-utils'; ++import { render, waitFor } from '@testing-library/react'; + import App from './App'; +@@ -22,4 +22,7 @@ describe('App', () => { + +- const rendered = await renderWithEffects(); +- expect(rendered.baseElement).toBeInTheDocument(); ++ const rendered = render(); ++ ++ await waitFor(() => { ++ expect(rendered.baseElement).toBeInTheDocument(); ++ }); + }); +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 9b65186..e24c468 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'; +@@ -15,5 +15,3 @@ import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +-import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -22,2 +20,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,8 +28,12 @@ import { Root } from './components/Root'; + +-import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; ++import { ++ AlertDisplay, ++ OAuthRequestDialog, ++ SignInPage, ++} 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 +45,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -47,2 +52,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -52,10 +58,10 @@ const app = createApp({ + }, ++ components: { ++ SignInPage: props => , ++ }, + }); + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -67,5 +73,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -77,9 +85,8 @@ const routes = ( + } +- /> +- } ++ element={ ++ ++ ++ ++ } + /> +@@ -93,4 +100,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -100,5 +107,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..853d175 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,22 +1,5 @@ +-/* +- * 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'; + import ExtensionIcon from '@material-ui/icons/Extension'; +-import MapIcon from '@material-ui/icons/MyLocation'; + import LibraryBooks from '@material-ui/icons/LibraryBooks'; +@@ -25,3 +8,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -34,3 +16,2 @@ import { + sidebarConfig, +- SidebarContext, + SidebarDivider, +@@ -41,2 +22,4 @@ import { + SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -44,2 +27,4 @@ import MenuIcon from '@material-ui/icons/Menu'; + import SearchIcon from '@material-ui/icons/Search'; ++import { MyGroupsSidebarItem } from '@backstage/plugin-org'; ++import GroupIcon from '@material-ui/icons/People'; + +@@ -62,3 +47,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -66,8 +51,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -89,2 +69,7 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + ++ + +@@ -95,3 +80,3 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ {/* Items in this group will be scrollable if they run out of space */} + +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..6164c76 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,7 +27,5 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +-import { +- isGithubActionsAvailable, +- EntityGithubActionsContent, +-} from '@backstage/plugin-github-actions'; + import { +@@ -70,2 +53,18 @@ import { + ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++import { ++ EntityKubernetesContent, ++ isKubernetesAvailable, ++} from '@backstage/plugin-kubernetes'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); ++ + const cicdContent = ( +@@ -74,5 +73,9 @@ const cicdContent = ( + +- +- +- ++ {/* ++ Here you can add support for different CI/CD services, for example ++ using @backstage-community/plugin-github-actions as follows: ++ ++ ++ ++ */} + +@@ -107,2 +110,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -146,2 +157,10 @@ const serviceEntityPage = ( + ++ ++ ++ ++ + +@@ -169,3 +188,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -184,2 +203,10 @@ const websiteEntityPage = ( + ++ ++ ++ ++ + +@@ -196,3 +223,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -215,3 +242,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -296,5 +323,8 @@ const groupPage = ( + +- ++ + + ++ ++ ++ + +@@ -315,3 +345,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/app/src/index.tsx b/packages/app/src/index.tsx +index b16aaf7..d875c77 100644 +--- a/packages/app/src/index.tsx ++++ b/packages/app/src/index.tsx +@@ -2,5 +2,5 @@ import '@backstage/cli/asset-types'; + import React from 'react'; +-import ReactDOM from 'react-dom'; ++import ReactDOM from 'react-dom/client'; + import App from './App'; + +-ReactDOM.render(, document.getElementById('root')); ++ReactDOM.createRoot(document.getElementById('root')!).render(); +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..bef67b5 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,15 @@ 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 ++ ++# This will include the examples, if you don't need these simply remove this line ++COPY --chown=node:node examples ./examples + + # 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..6a14abc 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,35 +6,43 @@ + "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.24.0", ++ "@backstage/backend-defaults": "^0.4.2", ++ "@backstage/backend-tasks": "^0.6.0", ++ "@backstage/config": "^1.2.0", ++ "@backstage/plugin-app-backend": "^0.3.72", ++ "@backstage/plugin-auth-backend": "^0.22.10", ++ "@backstage/plugin-auth-backend-module-github-provider": "^0.1.20", ++ "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.9", ++ "@backstage/plugin-auth-node": "^0.5.0", ++ "@backstage/plugin-catalog-backend": "^1.25.0", ++ "@backstage/plugin-catalog-backend-module-logs": "^0.0.2", ++ "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.21", ++ "@backstage/plugin-kubernetes-backend": "^0.18.4", ++ "@backstage/plugin-permission-backend": "^0.5.47", ++ "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.1.20", ++ "@backstage/plugin-permission-common": "^0.8.1", ++ "@backstage/plugin-permission-node": "^0.8.1", ++ "@backstage/plugin-proxy-backend": "^0.5.4", ++ "@backstage/plugin-scaffolder-backend": "^1.24.0", ++ "@backstage/plugin-search-backend": "^1.5.15", ++ "@backstage/plugin-search-backend-module-catalog": "^0.2.0", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.33", ++ "@backstage/plugin-search-backend-module-techdocs": "^0.2.0", ++ "@backstage/plugin-search-backend-node": "^1.3.0", ++ "@backstage/plugin-techdocs-backend": "^1.10.10", + "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", +- "dockerode": "^3.3.1", +- "express": "^4.17.1", +- "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "@vscode/sqlite3": "^5.0.7", ++ "better-sqlite3": "^9.0.0", ++ "node-gyp": "^10.0.0", ++ "pg": "^8.11.3", + "winston": "^3.2.1" +@@ -42,6 +50,6 @@ + "devDependencies": { +- "@backstage/cli": "^0.14.0", +- "@types/dockerode": "^3.3.0", ++ "@backstage/cli": "^0.27.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.test.ts b/packages/backend/src/index.test.ts +deleted file mode 100644 +index 7814b8c..0000000 +--- a/packages/backend/src/index.test.ts ++++ /dev/null +@@ -1,8 +0,0 @@ +-import { PluginEnvironment } from './types'; +- +-describe('test', () => { +- it('unbreaks the test runner', () => { +- const unbreaker = {} as PluginEnvironment; +- expect(unbreaker).toBeTruthy(); +- }); +-}); +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 70bc66b..f57d4c5 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -8,102 +8,47 @@ + +-import Router from 'express-promise-router'; +-import { +- createServiceBuilder, +- loadBackendConfig, +- getRootLogger, +- useHotMemoize, +- notFoundHandler, +- CacheManager, +- DatabaseManager, +- SingleHostDiscovery, +- UrlReaders, +- ServerTokenManager, +-} from '@backstage/backend-common'; +-import { TaskScheduler } from '@backstage/backend-tasks'; +-import { Config } from '@backstage/config'; +-import app from './plugins/app'; +-import auth from './plugins/auth'; +-import catalog from './plugins/catalog'; +-import scaffolder from './plugins/scaffolder'; +-import proxy from './plugins/proxy'; +-import techdocs from './plugins/techdocs'; +-import search from './plugins/search'; +-import { PluginEnvironment } from './types'; +-import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { createBackend } from '@backstage/backend-defaults'; + +-function makeCreateEnv(config: Config) { +- const root = getRootLogger(); +- const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); +- const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); +- const permissions = ServerPermissionClient.fromConfig(config, { +- discovery, +- tokenManager, +- }); ++const backend = createBackend(); + +- root.info(`Created UrlReader ${reader}`); ++backend.add(import('@backstage/plugin-app-backend/alpha')); ++backend.add(import('@backstage/plugin-proxy-backend/alpha')); ++backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); ++backend.add(import('@backstage/plugin-techdocs-backend/alpha')); + +- return (plugin: string): PluginEnvironment => { +- const logger = root.child({ type: 'plugin', plugin }); +- const database = databaseManager.forPlugin(plugin); +- const cache = cacheManager.forPlugin(plugin); +- const scheduler = taskScheduler.forPlugin(plugin); +- return { +- logger, +- database, +- cache, +- config, +- reader, +- discovery, +- tokenManager, +- scheduler, +- permissions, +- }; +- }; +-} ++// auth plugin ++backend.add(import('@backstage/plugin-auth-backend')); ++// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin ++backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); ++// See https://backstage.io/docs/auth/guest/provider + +-async function main() { +- const config = await loadBackendConfig({ +- argv: process.argv, +- logger: getRootLogger(), +- }); +- const createEnv = makeCreateEnv(config); ++// catalog plugin ++backend.add(import('@backstage/plugin-catalog-backend/alpha')); ++backend.add( ++ import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ++); + +- const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); +- const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); +- const authEnv = useHotMemoize(module, () => createEnv('auth')); +- const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); +- const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); +- const searchEnv = useHotMemoize(module, () => createEnv('search')); +- const appEnv = useHotMemoize(module, () => createEnv('app')); ++// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors ++backend.add(import('@backstage/plugin-catalog-backend-module-logs')); + +- const apiRouter = Router(); +- apiRouter.use('/catalog', await catalog(catalogEnv)); +- apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); +- apiRouter.use('/auth', await auth(authEnv)); +- apiRouter.use('/techdocs', await techdocs(techdocsEnv)); +- apiRouter.use('/proxy', await proxy(proxyEnv)); +- apiRouter.use('/search', await search(searchEnv)); ++// permission plugin ++backend.add(import('@backstage/plugin-permission-backend/alpha')); ++// See https://backstage.io/docs/permissions/getting-started for how to create your own permission policy ++backend.add( ++ import('@backstage/plugin-permission-backend-module-allow-all-policy'), ++); + +- // Add backends ABOVE this line; this 404 handler is the catch-all fallback +- apiRouter.use(notFoundHandler()); ++// search plugin ++backend.add(import('@backstage/plugin-search-backend/alpha')); + +- const service = createServiceBuilder(module) +- .loadConfig(config) +- .addRouter('/api', apiRouter) +- .addRouter('', await app(appEnv)); ++// search engine ++// See https://backstage.io/docs/features/search/search-engines ++backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); + +- await service.start().catch(err => { +- console.log(err); +- process.exit(1); +- }); +-} ++// search collators ++backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); ++backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); + +-module.hot?.accept(); +-main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); +- process.exit(1); +-}); ++// kubernetes ++backend.add(import('@backstage/plugin-kubernetes-backend/alpha')); ++ ++backend.start(); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +deleted file mode 100644 +index 14e19a1..0000000 +--- a/packages/backend/src/plugins/app.ts ++++ /dev/null +@@ -1,16 +0,0 @@ +-import { createRouter } from '@backstage/plugin-app-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- database, +-}: PluginEnvironment): Promise { +- return await createRouter({ +- logger, +- config, +- database, +- appPackageName: 'app', +- }); +-} +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +deleted file mode 100644 +index 015c864..0000000 +--- a/packages/backend/src/plugins/auth.ts ++++ /dev/null +@@ -1,19 +0,0 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +- tokenManager, +-}: PluginEnvironment): Promise { +- return await createRouter({ +- logger, +- config, +- database, +- discovery, +- tokenManager, +- }); +-} +diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts +deleted file mode 100644 +index 876cb6b..0000000 +--- a/packages/backend/src/plugins/catalog.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +-import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const builder = await CatalogBuilder.create(env); +- builder.addProcessor(new ScaffolderEntitiesProcessor()); +- const { processingEngine, router } = await builder.build(); +- await processingEngine.start(); +- return router; +-} +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +deleted file mode 100644 +index 506f6d9..0000000 +--- a/packages/backend/src/plugins/proxy.ts ++++ /dev/null +@@ -1,11 +0,0 @@ +-import { createRouter } from '@backstage/plugin-proxy-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); +-} +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +deleted file mode 100644 +index 6be2e97..0000000 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ /dev/null +@@ -1,27 +0,0 @@ +-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'; +-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 }); +- +- return await createRouter({ +- containerRunner, +- logger, +- config, +- database, +- catalogClient, +- reader, +- }); +-} +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +deleted file mode 100644 +index a0a1cc3..0000000 +--- a/packages/backend/src/plugins/search.ts ++++ /dev/null +@@ -1,58 +0,0 @@ +-import { useHotCleanup } from '@backstage/backend-common'; +-import { createRouter } from '@backstage/plugin-search-backend'; +-import { +- IndexBuilder, +- LunrSearchEngine, +-} from '@backstage/plugin-search-backend-node'; +-import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend'; +- +-export default async function createPlugin({ +- logger, +- permissions, +- discovery, +- config, +- tokenManager, +-}: PluginEnvironment) { +- // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); +- +- // Collators are responsible for gathering documents known to plugins. This +- // collator gathers entities from the software catalog. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { +- discovery, +- tokenManager, +- }), +- }); +- +- // collator gathers entities from techdocs. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultTechDocsCollator.fromConfig(config, { +- discovery, +- logger, +- tokenManager, +- }), +- }); +- +- // The scheduler controls when documents are gathered from collators and sent +- // to the search engine for indexing. +- const { scheduler } = await indexBuilder.build(); +- +- // 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()); +- +- return await createRouter({ +- engine: indexBuilder.getSearchEngine(), +- types: indexBuilder.getDocumentTypes(), +- permissions, +- config, +- logger, +- }); +-} +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +deleted file mode 100644 +index 054c64d..0000000 +--- a/packages/backend/src/plugins/techdocs.ts ++++ /dev/null +@@ -1,55 +0,0 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; +-import { +- createRouter, +- Generators, +- Preparers, +- Publisher, +-} from '@backstage/plugin-techdocs-backend'; +-import Docker from 'dockerode'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +- cache, +-}: PluginEnvironment): Promise { +- // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, +- }); +- +- // Docker client (conditionally) used by the generators, based on techdocs.generators config. +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- +- // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, +- containerRunner, +- }); +- +- // Publisher is used for +- // 1. Publishing generated files to storage +- // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, +- }); +- +- // checks if the publisher is working and logs the result +- await publisher.getReadiness(); +- +- return await createRouter({ +- preparers, +- generators, +- publisher, +- logger, +- config, +- discovery, +- cache, +- }); +-} +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +deleted file mode 100644 +index 0862b0e..0000000 +--- a/packages/backend/src/types.ts ++++ /dev/null +@@ -1,23 +0,0 @@ +-import { Logger } from 'winston'; +-import { Config } from '@backstage/config'; +-import { +- PluginCacheManager, +- PluginDatabaseManager, +- PluginEndpointDiscovery, +- TokenManager, +- UrlReader, +-} from '@backstage/backend-common'; +-import { PluginTaskScheduler } from '@backstage/backend-tasks'; +-import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; +- +-export type PluginEnvironment = { +- logger: Logger; +- database: PluginDatabaseManager; +- cache: PluginCacheManager; +- config: Config; +- reader: UrlReader; +- discovery: PluginEndpointDiscovery; +- tokenManager: TokenManager; +- scheduler: PluginTaskScheduler; +- permissions: PermissionAuthorizer; +-}; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..733be13 +--- /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 dev', ++ 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..c37fc61 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1,3 @@ ++# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. ++# yarn lockfile v1 ++ diff --git a/diffs/0.4.21..0.5.18.diff b/diffs/0.4.21..0.5.18.diff new file mode 100644 index 00000000..fb07665c --- /dev/null +++ b/diffs/0.4.21..0.5.18.diff @@ -0,0 +1,1930 @@ +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..5d426f5 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,51 @@ 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/apis/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 ++ ++auth: ++ providers: ++ guest: {} ++ ++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: ++ # Local example data, replace this with your production config, these are intended for demo use only. ++ # File locations are relative to the backend process, typically in a deployed context, such as in a Docker container, this will be the root ++ - type: file ++ target: ./examples/entities.yaml ++ ++ # Local example template ++ - type: file ++ target: ./examples/template/template.yaml ++ rules: ++ - allow: [Template] ++ ++ # Local example organizational data ++ - type: file ++ target: ./examples/org.yaml ++ rules: ++ - allow: [User, Group] +diff --git a/app-config.yaml b/app-config.yaml +index d45e354..ca52ec5 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/integrations/github/locations#configuration + 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 + +@@ -59,3 +66,5 @@ auth: + # see https://backstage.io/docs/auth/ to learn about auth providers +- providers: {} ++ providers: ++ # See https://backstage.io/docs/auth/guest/provider ++ guest: {} + +@@ -69,40 +78,36 @@ 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 ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml ++ rules: ++ - allow: [Template] + +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml ++ rules: ++ - allow: [User, Group] + +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml + +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.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] + +- # 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 +- 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 +- rules: +- - allow: [Template] ++kubernetes: ++ # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options ++ ++# see https://backstage.io/docs/permissions/getting-started for more on the permission framework ++permission: ++ # setting this to `false` will disable permissions ++ enabled: true +diff --git a/backstage.json b/backstage.json +index bc89f03..230d39d 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "0.4.21" ++ "version": "1.30.0" + } +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..80a963e 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.27.0", ++ "@backstage/e2e-test-utils": "^0.1.1", ++ "@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": "^10.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.5.4" ++ "typescript": "~5.4.0" ++ }, ++ "resolutions": { ++ "@types/react": "^18", ++ "@types/react-dom": "^18" + }, +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..839ff88 +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,27 @@ ++/* ++ * 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('/'); ++ ++ const enterButton = page.getByRole('button', { name: 'Enter' }); ++ await expect(enterButton).toBeVisible(); ++ await enterButton.click(); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index cfaf39c..346ffc1 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,25 +5,37 @@ + "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.5.10", ++ "@backstage/catalog-model": "^1.6.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/core-app-api": "^1.14.2", ++ "@backstage/core-components": "^0.14.10", ++ "@backstage/core-plugin-api": "^1.9.3", ++ "@backstage/integration-react": "^1.1.30", ++ "@backstage/plugin-api-docs": "^0.11.8", ++ "@backstage/plugin-catalog": "^1.22.0", ++ "@backstage/plugin-catalog-common": "^1.0.26", ++ "@backstage/plugin-catalog-graph": "^0.4.8", ++ "@backstage/plugin-catalog-import": "^0.12.2", ++ "@backstage/plugin-catalog-react": "^1.12.3", ++ "@backstage/plugin-kubernetes": "^0.11.13", ++ "@backstage/plugin-org": "^0.6.28", ++ "@backstage/plugin-permission-react": "^0.4.25", ++ "@backstage/plugin-scaffolder": "^1.24.0", ++ "@backstage/plugin-search": "^1.4.15", ++ "@backstage/plugin-search-react": "^1.7.14", ++ "@backstage/plugin-techdocs": "^1.10.8", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.13", ++ "@backstage/plugin-techdocs-react": "^1.2.7", ++ "@backstage/plugin-user-settings": "^0.8.11", ++ "@backstage/theme": "^0.5.6", + "@material-ui/core": "^4.12.2", +@@ -31,31 +43,17 @@ + "history": "^5.0.0", +- "react": "^17.0.2", +- "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": "^18.0.2", ++ "react-dom": "^18.0.2", ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^0.2.6", +- "@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", ++ "@backstage/test-utils": "^1.5.10", ++ "@playwright/test": "^1.32.3", ++ "@testing-library/jest-dom": "^6.0.0", ++ "@testing-library/react": "^14.0.0", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^9.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..18da7c4 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -8,5 +8,4 @@ + name="description" +- content="Backstage is an open platform for building developer portals" ++ content="Backstage is an open source framework for building developer portals" + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index b94cac7..ec8ba1d 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { renderWithEffects } from '@backstage/test-utils'; ++import { render, waitFor } from '@testing-library/react'; + import App from './App'; +@@ -22,4 +22,7 @@ describe('App', () => { + +- const rendered = await renderWithEffects(); +- expect(rendered.baseElement).toBeInTheDocument(); ++ const rendered = render(); ++ ++ await waitFor(() => { ++ expect(rendered.baseElement).toBeInTheDocument(); ++ }); + }); +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 9b65186..e24c468 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'; +@@ -15,5 +15,3 @@ import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +-import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -22,2 +20,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,8 +28,12 @@ import { Root } from './components/Root'; + +-import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; ++import { ++ AlertDisplay, ++ OAuthRequestDialog, ++ SignInPage, ++} 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 +45,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -47,2 +52,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -52,10 +58,10 @@ const app = createApp({ + }, ++ components: { ++ SignInPage: props => , ++ }, + }); + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -67,5 +73,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -77,9 +85,8 @@ const routes = ( + } +- /> +- } ++ element={ ++ ++ ++ ++ } + /> +@@ -93,4 +100,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -100,5 +107,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..853d175 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,22 +1,5 @@ +-/* +- * 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'; + import ExtensionIcon from '@material-ui/icons/Extension'; +-import MapIcon from '@material-ui/icons/MyLocation'; + import LibraryBooks from '@material-ui/icons/LibraryBooks'; +@@ -25,3 +8,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -34,3 +16,2 @@ import { + sidebarConfig, +- SidebarContext, + SidebarDivider, +@@ -41,2 +22,4 @@ import { + SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -44,2 +27,4 @@ import MenuIcon from '@material-ui/icons/Menu'; + import SearchIcon from '@material-ui/icons/Search'; ++import { MyGroupsSidebarItem } from '@backstage/plugin-org'; ++import GroupIcon from '@material-ui/icons/People'; + +@@ -62,3 +47,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -66,8 +51,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -89,2 +69,7 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + ++ + +@@ -95,3 +80,3 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ {/* Items in this group will be scrollable if they run out of space */} + +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..6164c76 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,7 +27,5 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +-import { +- isGithubActionsAvailable, +- EntityGithubActionsContent, +-} from '@backstage/plugin-github-actions'; + import { +@@ -70,2 +53,18 @@ import { + ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++import { ++ EntityKubernetesContent, ++ isKubernetesAvailable, ++} from '@backstage/plugin-kubernetes'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); ++ + const cicdContent = ( +@@ -74,5 +73,9 @@ const cicdContent = ( + +- +- +- ++ {/* ++ Here you can add support for different CI/CD services, for example ++ using @backstage-community/plugin-github-actions as follows: ++ ++ ++ ++ */} + +@@ -107,2 +110,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -146,2 +157,10 @@ const serviceEntityPage = ( + ++ ++ ++ ++ + +@@ -169,3 +188,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -184,2 +203,10 @@ const websiteEntityPage = ( + ++ ++ ++ ++ + +@@ -196,3 +223,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -215,3 +242,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -296,5 +323,8 @@ const groupPage = ( + +- ++ + + ++ ++ ++ + +@@ -315,3 +345,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/app/src/index.tsx b/packages/app/src/index.tsx +index b16aaf7..d875c77 100644 +--- a/packages/app/src/index.tsx ++++ b/packages/app/src/index.tsx +@@ -2,5 +2,5 @@ import '@backstage/cli/asset-types'; + import React from 'react'; +-import ReactDOM from 'react-dom'; ++import ReactDOM from 'react-dom/client'; + import App from './App'; + +-ReactDOM.render(, document.getElementById('root')); ++ReactDOM.createRoot(document.getElementById('root')!).render(); +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..bef67b5 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,15 @@ 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 --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++# This will include the examples, if you don't need these simply remove this line ++COPY --chown=node:node examples ./examples + + # 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..6a14abc 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,35 +6,43 @@ + "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.24.0", ++ "@backstage/backend-defaults": "^0.4.2", ++ "@backstage/backend-tasks": "^0.6.0", ++ "@backstage/config": "^1.2.0", ++ "@backstage/plugin-app-backend": "^0.3.72", ++ "@backstage/plugin-auth-backend": "^0.22.10", ++ "@backstage/plugin-auth-backend-module-github-provider": "^0.1.20", ++ "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.9", ++ "@backstage/plugin-auth-node": "^0.5.0", ++ "@backstage/plugin-catalog-backend": "^1.25.0", ++ "@backstage/plugin-catalog-backend-module-logs": "^0.0.2", ++ "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.21", ++ "@backstage/plugin-kubernetes-backend": "^0.18.4", ++ "@backstage/plugin-permission-backend": "^0.5.47", ++ "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.1.20", ++ "@backstage/plugin-permission-common": "^0.8.1", ++ "@backstage/plugin-permission-node": "^0.8.1", ++ "@backstage/plugin-proxy-backend": "^0.5.4", ++ "@backstage/plugin-scaffolder-backend": "^1.24.0", ++ "@backstage/plugin-search-backend": "^1.5.15", ++ "@backstage/plugin-search-backend-module-catalog": "^0.2.0", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.33", ++ "@backstage/plugin-search-backend-module-techdocs": "^0.2.0", ++ "@backstage/plugin-search-backend-node": "^1.3.0", ++ "@backstage/plugin-techdocs-backend": "^1.10.10", + "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", +- "dockerode": "^3.3.1", +- "express": "^4.17.1", +- "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "@vscode/sqlite3": "^5.0.7", ++ "better-sqlite3": "^9.0.0", ++ "node-gyp": "^10.0.0", ++ "pg": "^8.11.3", + "winston": "^3.2.1" +@@ -42,6 +50,6 @@ + "devDependencies": { +- "@backstage/cli": "^0.14.1", +- "@types/dockerode": "^3.3.0", ++ "@backstage/cli": "^0.27.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.test.ts b/packages/backend/src/index.test.ts +deleted file mode 100644 +index 7814b8c..0000000 +--- a/packages/backend/src/index.test.ts ++++ /dev/null +@@ -1,8 +0,0 @@ +-import { PluginEnvironment } from './types'; +- +-describe('test', () => { +- it('unbreaks the test runner', () => { +- const unbreaker = {} as PluginEnvironment; +- expect(unbreaker).toBeTruthy(); +- }); +-}); +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 70bc66b..f57d4c5 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -8,102 +8,47 @@ + +-import Router from 'express-promise-router'; +-import { +- createServiceBuilder, +- loadBackendConfig, +- getRootLogger, +- useHotMemoize, +- notFoundHandler, +- CacheManager, +- DatabaseManager, +- SingleHostDiscovery, +- UrlReaders, +- ServerTokenManager, +-} from '@backstage/backend-common'; +-import { TaskScheduler } from '@backstage/backend-tasks'; +-import { Config } from '@backstage/config'; +-import app from './plugins/app'; +-import auth from './plugins/auth'; +-import catalog from './plugins/catalog'; +-import scaffolder from './plugins/scaffolder'; +-import proxy from './plugins/proxy'; +-import techdocs from './plugins/techdocs'; +-import search from './plugins/search'; +-import { PluginEnvironment } from './types'; +-import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { createBackend } from '@backstage/backend-defaults'; + +-function makeCreateEnv(config: Config) { +- const root = getRootLogger(); +- const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); +- const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); +- const permissions = ServerPermissionClient.fromConfig(config, { +- discovery, +- tokenManager, +- }); ++const backend = createBackend(); + +- root.info(`Created UrlReader ${reader}`); ++backend.add(import('@backstage/plugin-app-backend/alpha')); ++backend.add(import('@backstage/plugin-proxy-backend/alpha')); ++backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); ++backend.add(import('@backstage/plugin-techdocs-backend/alpha')); + +- return (plugin: string): PluginEnvironment => { +- const logger = root.child({ type: 'plugin', plugin }); +- const database = databaseManager.forPlugin(plugin); +- const cache = cacheManager.forPlugin(plugin); +- const scheduler = taskScheduler.forPlugin(plugin); +- return { +- logger, +- database, +- cache, +- config, +- reader, +- discovery, +- tokenManager, +- scheduler, +- permissions, +- }; +- }; +-} ++// auth plugin ++backend.add(import('@backstage/plugin-auth-backend')); ++// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin ++backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); ++// See https://backstage.io/docs/auth/guest/provider + +-async function main() { +- const config = await loadBackendConfig({ +- argv: process.argv, +- logger: getRootLogger(), +- }); +- const createEnv = makeCreateEnv(config); ++// catalog plugin ++backend.add(import('@backstage/plugin-catalog-backend/alpha')); ++backend.add( ++ import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ++); + +- const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); +- const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); +- const authEnv = useHotMemoize(module, () => createEnv('auth')); +- const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); +- const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); +- const searchEnv = useHotMemoize(module, () => createEnv('search')); +- const appEnv = useHotMemoize(module, () => createEnv('app')); ++// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors ++backend.add(import('@backstage/plugin-catalog-backend-module-logs')); + +- const apiRouter = Router(); +- apiRouter.use('/catalog', await catalog(catalogEnv)); +- apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); +- apiRouter.use('/auth', await auth(authEnv)); +- apiRouter.use('/techdocs', await techdocs(techdocsEnv)); +- apiRouter.use('/proxy', await proxy(proxyEnv)); +- apiRouter.use('/search', await search(searchEnv)); ++// permission plugin ++backend.add(import('@backstage/plugin-permission-backend/alpha')); ++// See https://backstage.io/docs/permissions/getting-started for how to create your own permission policy ++backend.add( ++ import('@backstage/plugin-permission-backend-module-allow-all-policy'), ++); + +- // Add backends ABOVE this line; this 404 handler is the catch-all fallback +- apiRouter.use(notFoundHandler()); ++// search plugin ++backend.add(import('@backstage/plugin-search-backend/alpha')); + +- const service = createServiceBuilder(module) +- .loadConfig(config) +- .addRouter('/api', apiRouter) +- .addRouter('', await app(appEnv)); ++// search engine ++// See https://backstage.io/docs/features/search/search-engines ++backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); + +- await service.start().catch(err => { +- console.log(err); +- process.exit(1); +- }); +-} ++// search collators ++backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); ++backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); + +-module.hot?.accept(); +-main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); +- process.exit(1); +-}); ++// kubernetes ++backend.add(import('@backstage/plugin-kubernetes-backend/alpha')); ++ ++backend.start(); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +deleted file mode 100644 +index 14e19a1..0000000 +--- a/packages/backend/src/plugins/app.ts ++++ /dev/null +@@ -1,16 +0,0 @@ +-import { createRouter } from '@backstage/plugin-app-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- database, +-}: PluginEnvironment): Promise { +- return await createRouter({ +- logger, +- config, +- database, +- appPackageName: 'app', +- }); +-} +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +deleted file mode 100644 +index 015c864..0000000 +--- a/packages/backend/src/plugins/auth.ts ++++ /dev/null +@@ -1,19 +0,0 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +- tokenManager, +-}: PluginEnvironment): Promise { +- return await createRouter({ +- logger, +- config, +- database, +- discovery, +- tokenManager, +- }); +-} +diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts +deleted file mode 100644 +index 876cb6b..0000000 +--- a/packages/backend/src/plugins/catalog.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +-import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const builder = await CatalogBuilder.create(env); +- builder.addProcessor(new ScaffolderEntitiesProcessor()); +- const { processingEngine, router } = await builder.build(); +- await processingEngine.start(); +- return router; +-} +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +deleted file mode 100644 +index 506f6d9..0000000 +--- a/packages/backend/src/plugins/proxy.ts ++++ /dev/null +@@ -1,11 +0,0 @@ +-import { createRouter } from '@backstage/plugin-proxy-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); +-} +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +deleted file mode 100644 +index 6be2e97..0000000 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ /dev/null +@@ -1,27 +0,0 @@ +-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'; +-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 }); +- +- return await createRouter({ +- containerRunner, +- logger, +- config, +- database, +- catalogClient, +- reader, +- }); +-} +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +deleted file mode 100644 +index a0a1cc3..0000000 +--- a/packages/backend/src/plugins/search.ts ++++ /dev/null +@@ -1,58 +0,0 @@ +-import { useHotCleanup } from '@backstage/backend-common'; +-import { createRouter } from '@backstage/plugin-search-backend'; +-import { +- IndexBuilder, +- LunrSearchEngine, +-} from '@backstage/plugin-search-backend-node'; +-import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend'; +- +-export default async function createPlugin({ +- logger, +- permissions, +- discovery, +- config, +- tokenManager, +-}: PluginEnvironment) { +- // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); +- +- // Collators are responsible for gathering documents known to plugins. This +- // collator gathers entities from the software catalog. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { +- discovery, +- tokenManager, +- }), +- }); +- +- // collator gathers entities from techdocs. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultTechDocsCollator.fromConfig(config, { +- discovery, +- logger, +- tokenManager, +- }), +- }); +- +- // The scheduler controls when documents are gathered from collators and sent +- // to the search engine for indexing. +- const { scheduler } = await indexBuilder.build(); +- +- // 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()); +- +- return await createRouter({ +- engine: indexBuilder.getSearchEngine(), +- types: indexBuilder.getDocumentTypes(), +- permissions, +- config, +- logger, +- }); +-} +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +deleted file mode 100644 +index 054c64d..0000000 +--- a/packages/backend/src/plugins/techdocs.ts ++++ /dev/null +@@ -1,55 +0,0 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; +-import { +- createRouter, +- Generators, +- Preparers, +- Publisher, +-} from '@backstage/plugin-techdocs-backend'; +-import Docker from 'dockerode'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +- cache, +-}: PluginEnvironment): Promise { +- // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, +- }); +- +- // Docker client (conditionally) used by the generators, based on techdocs.generators config. +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- +- // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, +- containerRunner, +- }); +- +- // Publisher is used for +- // 1. Publishing generated files to storage +- // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, +- }); +- +- // checks if the publisher is working and logs the result +- await publisher.getReadiness(); +- +- return await createRouter({ +- preparers, +- generators, +- publisher, +- logger, +- config, +- discovery, +- cache, +- }); +-} +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +deleted file mode 100644 +index 0862b0e..0000000 +--- a/packages/backend/src/types.ts ++++ /dev/null +@@ -1,23 +0,0 @@ +-import { Logger } from 'winston'; +-import { Config } from '@backstage/config'; +-import { +- PluginCacheManager, +- PluginDatabaseManager, +- PluginEndpointDiscovery, +- TokenManager, +- UrlReader, +-} from '@backstage/backend-common'; +-import { PluginTaskScheduler } from '@backstage/backend-tasks'; +-import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; +- +-export type PluginEnvironment = { +- logger: Logger; +- database: PluginDatabaseManager; +- cache: PluginCacheManager; +- config: Config; +- reader: UrlReader; +- discovery: PluginEndpointDiscovery; +- tokenManager: TokenManager; +- scheduler: PluginTaskScheduler; +- permissions: PermissionAuthorizer; +-}; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..733be13 +--- /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 dev', ++ 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..c37fc61 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1,3 @@ ++# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. ++# yarn lockfile v1 ++ diff --git a/diffs/0.4.22..0.5.18.diff b/diffs/0.4.22..0.5.18.diff new file mode 100644 index 00000000..e6cbed9d --- /dev/null +++ b/diffs/0.4.22..0.5.18.diff @@ -0,0 +1,1916 @@ +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..5d426f5 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,51 @@ 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/apis/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 ++ ++auth: ++ providers: ++ guest: {} ++ ++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: ++ # Local example data, replace this with your production config, these are intended for demo use only. ++ # File locations are relative to the backend process, typically in a deployed context, such as in a Docker container, this will be the root ++ - type: file ++ target: ./examples/entities.yaml ++ ++ # Local example template ++ - type: file ++ target: ./examples/template/template.yaml ++ rules: ++ - allow: [Template] ++ ++ # Local example organizational data ++ - type: file ++ target: ./examples/org.yaml ++ rules: ++ - allow: [User, Group] +diff --git a/app-config.yaml b/app-config.yaml +index d45e354..ca52ec5 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/integrations/github/locations#configuration + 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 + +@@ -59,3 +66,5 @@ auth: + # see https://backstage.io/docs/auth/ to learn about auth providers +- providers: {} ++ providers: ++ # See https://backstage.io/docs/auth/guest/provider ++ guest: {} + +@@ -69,40 +78,36 @@ 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 ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml ++ rules: ++ - allow: [Template] + +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml ++ rules: ++ - allow: [User, Group] + +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml + +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.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] + +- # 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 +- 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 +- rules: +- - allow: [Template] ++kubernetes: ++ # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options ++ ++# see https://backstage.io/docs/permissions/getting-started for more on the permission framework ++permission: ++ # setting this to `false` will disable permissions ++ enabled: true +diff --git a/backstage.json b/backstage.json +index 0656bae..230d39d 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "0.4.22" ++ "version": "1.30.0" + } +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..80a963e 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.27.0", ++ "@backstage/e2e-test-utils": "^0.1.1", ++ "@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": "^10.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.5.4" ++ "typescript": "~5.4.0" ++ }, ++ "resolutions": { ++ "@types/react": "^18", ++ "@types/react-dom": "^18" + }, +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..839ff88 +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,27 @@ ++/* ++ * 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('/'); ++ ++ const enterButton = page.getByRole('button', { name: 'Enter' }); ++ await expect(enterButton).toBeVisible(); ++ await enterButton.click(); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 5070c85..346ffc1 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,25 +5,37 @@ + "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.5.10", ++ "@backstage/catalog-model": "^1.6.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/core-app-api": "^1.14.2", ++ "@backstage/core-components": "^0.14.10", ++ "@backstage/core-plugin-api": "^1.9.3", ++ "@backstage/integration-react": "^1.1.30", ++ "@backstage/plugin-api-docs": "^0.11.8", ++ "@backstage/plugin-catalog": "^1.22.0", ++ "@backstage/plugin-catalog-common": "^1.0.26", ++ "@backstage/plugin-catalog-graph": "^0.4.8", ++ "@backstage/plugin-catalog-import": "^0.12.2", ++ "@backstage/plugin-catalog-react": "^1.12.3", ++ "@backstage/plugin-kubernetes": "^0.11.13", ++ "@backstage/plugin-org": "^0.6.28", ++ "@backstage/plugin-permission-react": "^0.4.25", ++ "@backstage/plugin-scaffolder": "^1.24.0", ++ "@backstage/plugin-search": "^1.4.15", ++ "@backstage/plugin-search-react": "^1.7.14", ++ "@backstage/plugin-techdocs": "^1.10.8", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.13", ++ "@backstage/plugin-techdocs-react": "^1.2.7", ++ "@backstage/plugin-user-settings": "^0.8.11", ++ "@backstage/theme": "^0.5.6", + "@material-ui/core": "^4.12.2", +@@ -31,31 +43,17 @@ + "history": "^5.0.0", +- "react": "^17.0.2", +- "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": "^18.0.2", ++ "react-dom": "^18.0.2", ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^0.3.0", +- "@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", ++ "@backstage/test-utils": "^1.5.10", ++ "@playwright/test": "^1.32.3", ++ "@testing-library/jest-dom": "^6.0.0", ++ "@testing-library/react": "^14.0.0", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^9.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..18da7c4 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -8,5 +8,4 @@ + name="description" +- content="Backstage is an open platform for building developer portals" ++ content="Backstage is an open source framework for building developer portals" + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index b94cac7..ec8ba1d 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { renderWithEffects } from '@backstage/test-utils'; ++import { render, waitFor } from '@testing-library/react'; + import App from './App'; +@@ -22,4 +22,7 @@ describe('App', () => { + +- const rendered = await renderWithEffects(); +- expect(rendered.baseElement).toBeInTheDocument(); ++ const rendered = render(); ++ ++ await waitFor(() => { ++ expect(rendered.baseElement).toBeInTheDocument(); ++ }); + }); +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index f4ff424..e24c468 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'; +@@ -15,3 +15,2 @@ import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +-import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +@@ -21,2 +20,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'; +@@ -27,7 +28,11 @@ import { Root } from './components/Root'; + +-import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; ++import { ++ AlertDisplay, ++ OAuthRequestDialog, ++ SignInPage, ++} 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 +45,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -46,2 +52,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -51,10 +58,10 @@ const app = createApp({ + }, ++ components: { ++ SignInPage: props => , ++ }, + }); + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -70,3 +77,7 @@ const routes = ( + element={} +- /> ++ > ++ ++ ++ ++ + } /> +@@ -74,9 +85,8 @@ const routes = ( + } +- /> +- } ++ element={ ++ ++ ++ ++ } + /> +@@ -90,4 +100,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -97,5 +107,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..853d175 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,22 +1,5 @@ +-/* +- * 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'; + import ExtensionIcon from '@material-ui/icons/Extension'; +-import MapIcon from '@material-ui/icons/MyLocation'; + import LibraryBooks from '@material-ui/icons/LibraryBooks'; +@@ -25,3 +8,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -34,3 +16,2 @@ import { + sidebarConfig, +- SidebarContext, + SidebarDivider, +@@ -41,2 +22,4 @@ import { + SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -44,2 +27,4 @@ import MenuIcon from '@material-ui/icons/Menu'; + import SearchIcon from '@material-ui/icons/Search'; ++import { MyGroupsSidebarItem } from '@backstage/plugin-org'; ++import GroupIcon from '@material-ui/icons/People'; + +@@ -62,3 +47,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -66,8 +51,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -89,2 +69,7 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + ++ + +@@ -95,3 +80,3 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ {/* Items in this group will be scrollable if they run out of space */} + +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..6164c76 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,7 +27,5 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +-import { +- isGithubActionsAvailable, +- EntityGithubActionsContent, +-} from '@backstage/plugin-github-actions'; + import { +@@ -70,2 +53,18 @@ import { + ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++import { ++ EntityKubernetesContent, ++ isKubernetesAvailable, ++} from '@backstage/plugin-kubernetes'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); ++ + const cicdContent = ( +@@ -74,5 +73,9 @@ const cicdContent = ( + +- +- +- ++ {/* ++ Here you can add support for different CI/CD services, for example ++ using @backstage-community/plugin-github-actions as follows: ++ ++ ++ ++ */} + +@@ -107,2 +110,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -146,2 +157,10 @@ const serviceEntityPage = ( + ++ ++ ++ ++ + +@@ -169,3 +188,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -184,2 +203,10 @@ const websiteEntityPage = ( + ++ ++ ++ ++ + +@@ -196,3 +223,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -215,3 +242,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -296,5 +323,8 @@ const groupPage = ( + +- ++ + + ++ ++ ++ + +@@ -315,3 +345,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/app/src/index.tsx b/packages/app/src/index.tsx +index b16aaf7..d875c77 100644 +--- a/packages/app/src/index.tsx ++++ b/packages/app/src/index.tsx +@@ -2,5 +2,5 @@ import '@backstage/cli/asset-types'; + import React from 'react'; +-import ReactDOM from 'react-dom'; ++import ReactDOM from 'react-dom/client'; + import App from './App'; + +-ReactDOM.render(, document.getElementById('root')); ++ReactDOM.createRoot(document.getElementById('root')!).render(); +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..bef67b5 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,15 @@ 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 --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++# This will include the examples, if you don't need these simply remove this line ++COPY --chown=node:node examples ./examples + + # 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..6a14abc 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,35 +6,43 @@ + "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.24.0", ++ "@backstage/backend-defaults": "^0.4.2", ++ "@backstage/backend-tasks": "^0.6.0", ++ "@backstage/config": "^1.2.0", ++ "@backstage/plugin-app-backend": "^0.3.72", ++ "@backstage/plugin-auth-backend": "^0.22.10", ++ "@backstage/plugin-auth-backend-module-github-provider": "^0.1.20", ++ "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.9", ++ "@backstage/plugin-auth-node": "^0.5.0", ++ "@backstage/plugin-catalog-backend": "^1.25.0", ++ "@backstage/plugin-catalog-backend-module-logs": "^0.0.2", ++ "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.21", ++ "@backstage/plugin-kubernetes-backend": "^0.18.4", ++ "@backstage/plugin-permission-backend": "^0.5.47", ++ "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.1.20", ++ "@backstage/plugin-permission-common": "^0.8.1", ++ "@backstage/plugin-permission-node": "^0.8.1", ++ "@backstage/plugin-proxy-backend": "^0.5.4", ++ "@backstage/plugin-scaffolder-backend": "^1.24.0", ++ "@backstage/plugin-search-backend": "^1.5.15", ++ "@backstage/plugin-search-backend-module-catalog": "^0.2.0", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.33", ++ "@backstage/plugin-search-backend-module-techdocs": "^0.2.0", ++ "@backstage/plugin-search-backend-node": "^1.3.0", ++ "@backstage/plugin-techdocs-backend": "^1.10.10", + "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", +- "dockerode": "^3.3.1", +- "express": "^4.17.1", +- "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "@vscode/sqlite3": "^5.0.7", ++ "better-sqlite3": "^9.0.0", ++ "node-gyp": "^10.0.0", ++ "pg": "^8.11.3", + "winston": "^3.2.1" +@@ -42,6 +50,6 @@ + "devDependencies": { +- "@backstage/cli": "^0.15.0", +- "@types/dockerode": "^3.3.0", ++ "@backstage/cli": "^0.27.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.test.ts b/packages/backend/src/index.test.ts +deleted file mode 100644 +index 7814b8c..0000000 +--- a/packages/backend/src/index.test.ts ++++ /dev/null +@@ -1,8 +0,0 @@ +-import { PluginEnvironment } from './types'; +- +-describe('test', () => { +- it('unbreaks the test runner', () => { +- const unbreaker = {} as PluginEnvironment; +- expect(unbreaker).toBeTruthy(); +- }); +-}); +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 70bc66b..f57d4c5 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -8,102 +8,47 @@ + +-import Router from 'express-promise-router'; +-import { +- createServiceBuilder, +- loadBackendConfig, +- getRootLogger, +- useHotMemoize, +- notFoundHandler, +- CacheManager, +- DatabaseManager, +- SingleHostDiscovery, +- UrlReaders, +- ServerTokenManager, +-} from '@backstage/backend-common'; +-import { TaskScheduler } from '@backstage/backend-tasks'; +-import { Config } from '@backstage/config'; +-import app from './plugins/app'; +-import auth from './plugins/auth'; +-import catalog from './plugins/catalog'; +-import scaffolder from './plugins/scaffolder'; +-import proxy from './plugins/proxy'; +-import techdocs from './plugins/techdocs'; +-import search from './plugins/search'; +-import { PluginEnvironment } from './types'; +-import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { createBackend } from '@backstage/backend-defaults'; + +-function makeCreateEnv(config: Config) { +- const root = getRootLogger(); +- const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); +- const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); +- const permissions = ServerPermissionClient.fromConfig(config, { +- discovery, +- tokenManager, +- }); ++const backend = createBackend(); + +- root.info(`Created UrlReader ${reader}`); ++backend.add(import('@backstage/plugin-app-backend/alpha')); ++backend.add(import('@backstage/plugin-proxy-backend/alpha')); ++backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); ++backend.add(import('@backstage/plugin-techdocs-backend/alpha')); + +- return (plugin: string): PluginEnvironment => { +- const logger = root.child({ type: 'plugin', plugin }); +- const database = databaseManager.forPlugin(plugin); +- const cache = cacheManager.forPlugin(plugin); +- const scheduler = taskScheduler.forPlugin(plugin); +- return { +- logger, +- database, +- cache, +- config, +- reader, +- discovery, +- tokenManager, +- scheduler, +- permissions, +- }; +- }; +-} ++// auth plugin ++backend.add(import('@backstage/plugin-auth-backend')); ++// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin ++backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); ++// See https://backstage.io/docs/auth/guest/provider + +-async function main() { +- const config = await loadBackendConfig({ +- argv: process.argv, +- logger: getRootLogger(), +- }); +- const createEnv = makeCreateEnv(config); ++// catalog plugin ++backend.add(import('@backstage/plugin-catalog-backend/alpha')); ++backend.add( ++ import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ++); + +- const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); +- const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); +- const authEnv = useHotMemoize(module, () => createEnv('auth')); +- const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); +- const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); +- const searchEnv = useHotMemoize(module, () => createEnv('search')); +- const appEnv = useHotMemoize(module, () => createEnv('app')); ++// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors ++backend.add(import('@backstage/plugin-catalog-backend-module-logs')); + +- const apiRouter = Router(); +- apiRouter.use('/catalog', await catalog(catalogEnv)); +- apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); +- apiRouter.use('/auth', await auth(authEnv)); +- apiRouter.use('/techdocs', await techdocs(techdocsEnv)); +- apiRouter.use('/proxy', await proxy(proxyEnv)); +- apiRouter.use('/search', await search(searchEnv)); ++// permission plugin ++backend.add(import('@backstage/plugin-permission-backend/alpha')); ++// See https://backstage.io/docs/permissions/getting-started for how to create your own permission policy ++backend.add( ++ import('@backstage/plugin-permission-backend-module-allow-all-policy'), ++); + +- // Add backends ABOVE this line; this 404 handler is the catch-all fallback +- apiRouter.use(notFoundHandler()); ++// search plugin ++backend.add(import('@backstage/plugin-search-backend/alpha')); + +- const service = createServiceBuilder(module) +- .loadConfig(config) +- .addRouter('/api', apiRouter) +- .addRouter('', await app(appEnv)); ++// search engine ++// See https://backstage.io/docs/features/search/search-engines ++backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); + +- await service.start().catch(err => { +- console.log(err); +- process.exit(1); +- }); +-} ++// search collators ++backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); ++backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); + +-module.hot?.accept(); +-main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); +- process.exit(1); +-}); ++// kubernetes ++backend.add(import('@backstage/plugin-kubernetes-backend/alpha')); ++ ++backend.start(); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +deleted file mode 100644 +index 14e19a1..0000000 +--- a/packages/backend/src/plugins/app.ts ++++ /dev/null +@@ -1,16 +0,0 @@ +-import { createRouter } from '@backstage/plugin-app-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- database, +-}: PluginEnvironment): Promise { +- return await createRouter({ +- logger, +- config, +- database, +- appPackageName: 'app', +- }); +-} +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +deleted file mode 100644 +index 015c864..0000000 +--- a/packages/backend/src/plugins/auth.ts ++++ /dev/null +@@ -1,19 +0,0 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +- tokenManager, +-}: PluginEnvironment): Promise { +- return await createRouter({ +- logger, +- config, +- database, +- discovery, +- tokenManager, +- }); +-} +diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts +deleted file mode 100644 +index 876cb6b..0000000 +--- a/packages/backend/src/plugins/catalog.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +-import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const builder = await CatalogBuilder.create(env); +- builder.addProcessor(new ScaffolderEntitiesProcessor()); +- const { processingEngine, router } = await builder.build(); +- await processingEngine.start(); +- return router; +-} +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +deleted file mode 100644 +index 506f6d9..0000000 +--- a/packages/backend/src/plugins/proxy.ts ++++ /dev/null +@@ -1,11 +0,0 @@ +-import { createRouter } from '@backstage/plugin-proxy-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); +-} +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +deleted file mode 100644 +index 6be2e97..0000000 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ /dev/null +@@ -1,27 +0,0 @@ +-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'; +-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 }); +- +- return await createRouter({ +- containerRunner, +- logger, +- config, +- database, +- catalogClient, +- reader, +- }); +-} +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +deleted file mode 100644 +index c359cb4..0000000 +--- a/packages/backend/src/plugins/search.ts ++++ /dev/null +@@ -1,58 +0,0 @@ +-import { useHotCleanup } from '@backstage/backend-common'; +-import { createRouter } from '@backstage/plugin-search-backend'; +-import { +- IndexBuilder, +- LunrSearchEngine, +-} from '@backstage/plugin-search-backend-node'; +-import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; +- +-export default async function createPlugin({ +- logger, +- permissions, +- discovery, +- config, +- tokenManager, +-}: PluginEnvironment) { +- // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); +- +- // Collators are responsible for gathering documents known to plugins. This +- // collator gathers entities from the software catalog. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- factory: DefaultCatalogCollatorFactory.fromConfig(config, { +- discovery, +- tokenManager, +- }), +- }); +- +- // collator gathers entities from techdocs. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- factory: DefaultTechDocsCollatorFactory.fromConfig(config, { +- discovery, +- logger, +- tokenManager, +- }), +- }); +- +- // The scheduler controls when documents are gathered from collators and sent +- // to the search engine for indexing. +- const { scheduler } = await indexBuilder.build(); +- +- // 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()); +- +- return await createRouter({ +- engine: indexBuilder.getSearchEngine(), +- types: indexBuilder.getDocumentTypes(), +- permissions, +- config, +- logger, +- }); +-} +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +deleted file mode 100644 +index 054c64d..0000000 +--- a/packages/backend/src/plugins/techdocs.ts ++++ /dev/null +@@ -1,55 +0,0 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; +-import { +- createRouter, +- Generators, +- Preparers, +- Publisher, +-} from '@backstage/plugin-techdocs-backend'; +-import Docker from 'dockerode'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +- cache, +-}: PluginEnvironment): Promise { +- // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, +- }); +- +- // Docker client (conditionally) used by the generators, based on techdocs.generators config. +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- +- // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, +- containerRunner, +- }); +- +- // Publisher is used for +- // 1. Publishing generated files to storage +- // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, +- }); +- +- // checks if the publisher is working and logs the result +- await publisher.getReadiness(); +- +- return await createRouter({ +- preparers, +- generators, +- publisher, +- logger, +- config, +- discovery, +- cache, +- }); +-} +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +deleted file mode 100644 +index 0862b0e..0000000 +--- a/packages/backend/src/types.ts ++++ /dev/null +@@ -1,23 +0,0 @@ +-import { Logger } from 'winston'; +-import { Config } from '@backstage/config'; +-import { +- PluginCacheManager, +- PluginDatabaseManager, +- PluginEndpointDiscovery, +- TokenManager, +- UrlReader, +-} from '@backstage/backend-common'; +-import { PluginTaskScheduler } from '@backstage/backend-tasks'; +-import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; +- +-export type PluginEnvironment = { +- logger: Logger; +- database: PluginDatabaseManager; +- cache: PluginCacheManager; +- config: Config; +- reader: UrlReader; +- discovery: PluginEndpointDiscovery; +- tokenManager: TokenManager; +- scheduler: PluginTaskScheduler; +- permissions: PermissionAuthorizer; +-}; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..733be13 +--- /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 dev', ++ 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..c37fc61 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1,3 @@ ++# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. ++# yarn lockfile v1 ++ diff --git a/diffs/0.4.23..0.5.18.diff b/diffs/0.4.23..0.5.18.diff new file mode 100644 index 00000000..339ee02b --- /dev/null +++ b/diffs/0.4.23..0.5.18.diff @@ -0,0 +1,1869 @@ +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..5d426f5 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,51 @@ 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/apis/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 ++ ++auth: ++ providers: ++ guest: {} ++ ++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: ++ # Local example data, replace this with your production config, these are intended for demo use only. ++ # File locations are relative to the backend process, typically in a deployed context, such as in a Docker container, this will be the root ++ - type: file ++ target: ./examples/entities.yaml ++ ++ # Local example template ++ - type: file ++ target: ./examples/template/template.yaml ++ rules: ++ - allow: [Template] ++ ++ # Local example organizational data ++ - type: file ++ target: ./examples/org.yaml ++ rules: ++ - allow: [User, Group] +diff --git a/app-config.yaml b/app-config.yaml +index 64a661d..ca52ec5 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/integrations/github/locations#configuration + 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 + +@@ -59,3 +66,5 @@ auth: + # see https://backstage.io/docs/auth/ to learn about auth providers +- providers: {} ++ providers: ++ # See https://backstage.io/docs/auth/guest/provider ++ guest: {} + +@@ -69,36 +78,36 @@ 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 ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml ++ rules: ++ - allow: [Template] + +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml ++ rules: ++ - allow: [User, Group] + +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml + +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.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] + +- # 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 +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/docs-template/template.yaml +- rules: +- - allow: [Template] ++kubernetes: ++ # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options ++ ++# see https://backstage.io/docs/permissions/getting-started for more on the permission framework ++permission: ++ # setting this to `false` will disable permissions ++ enabled: true +diff --git a/backstage.json b/backstage.json +index 3666530..230d39d 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "0.71.0" ++ "version": "1.30.0" + } +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..80a963e 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.27.0", ++ "@backstage/e2e-test-utils": "^0.1.1", ++ "@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": "^10.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.5.4" ++ "typescript": "~5.4.0" ++ }, ++ "resolutions": { ++ "@types/react": "^18", ++ "@types/react-dom": "^18" + }, +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..839ff88 +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,27 @@ ++/* ++ * 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('/'); ++ ++ const enterButton = page.getByRole('button', { name: 'Enter' }); ++ await expect(enterButton).toBeVisible(); ++ await enterButton.click(); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index bb1da11..346ffc1 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -8,25 +8,34 @@ + }, ++ "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.5.10", ++ "@backstage/catalog-model": "^1.6.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/core-app-api": "^1.14.2", ++ "@backstage/core-components": "^0.14.10", ++ "@backstage/core-plugin-api": "^1.9.3", ++ "@backstage/integration-react": "^1.1.30", ++ "@backstage/plugin-api-docs": "^0.11.8", ++ "@backstage/plugin-catalog": "^1.22.0", ++ "@backstage/plugin-catalog-common": "^1.0.26", ++ "@backstage/plugin-catalog-graph": "^0.4.8", ++ "@backstage/plugin-catalog-import": "^0.12.2", ++ "@backstage/plugin-catalog-react": "^1.12.3", ++ "@backstage/plugin-kubernetes": "^0.11.13", ++ "@backstage/plugin-org": "^0.6.28", ++ "@backstage/plugin-permission-react": "^0.4.25", ++ "@backstage/plugin-scaffolder": "^1.24.0", ++ "@backstage/plugin-search": "^1.4.15", ++ "@backstage/plugin-search-react": "^1.7.14", ++ "@backstage/plugin-techdocs": "^1.10.8", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.13", ++ "@backstage/plugin-techdocs-react": "^1.2.7", ++ "@backstage/plugin-user-settings": "^0.8.11", ++ "@backstage/theme": "^0.5.6", + "@material-ui/core": "^4.12.2", +@@ -34,31 +43,17 @@ + "history": "^5.0.0", +- "react": "^17.0.2", +- "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": "^18.0.2", ++ "react-dom": "^18.0.2", ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^0.3.0", +- "@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", ++ "@backstage/test-utils": "^1.5.10", ++ "@playwright/test": "^1.32.3", ++ "@testing-library/jest-dom": "^6.0.0", ++ "@testing-library/react": "^14.0.0", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^9.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..18da7c4 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -8,5 +8,4 @@ + name="description" +- content="Backstage is an open platform for building developer portals" ++ content="Backstage is an open source framework for building developer portals" + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index b94cac7..ec8ba1d 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { renderWithEffects } from '@backstage/test-utils'; ++import { render, waitFor } from '@testing-library/react'; + import App from './App'; +@@ -22,4 +22,7 @@ describe('App', () => { + +- const rendered = await renderWithEffects(); +- expect(rendered.baseElement).toBeInTheDocument(); ++ const rendered = render(); ++ ++ await waitFor(() => { ++ expect(rendered.baseElement).toBeInTheDocument(); ++ }); + }); +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index f4ff424..e24c468 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'; +@@ -15,3 +15,2 @@ import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +-import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +@@ -21,2 +20,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'; +@@ -27,7 +28,11 @@ import { Root } from './components/Root'; + +-import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; ++import { ++ AlertDisplay, ++ OAuthRequestDialog, ++ SignInPage, ++} 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 +45,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -46,2 +52,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -51,10 +58,10 @@ const app = createApp({ + }, ++ components: { ++ SignInPage: props => , ++ }, + }); + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -70,3 +77,7 @@ const routes = ( + element={} +- /> ++ > ++ ++ ++ ++ + } /> +@@ -74,9 +85,8 @@ const routes = ( + } +- /> +- } ++ element={ ++ ++ ++ ++ } + /> +@@ -90,4 +100,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -97,5 +107,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..853d175 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,22 +1,5 @@ +-/* +- * 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'; + import ExtensionIcon from '@material-ui/icons/Extension'; +-import MapIcon from '@material-ui/icons/MyLocation'; + import LibraryBooks from '@material-ui/icons/LibraryBooks'; +@@ -25,3 +8,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -34,3 +16,2 @@ import { + sidebarConfig, +- SidebarContext, + SidebarDivider, +@@ -41,2 +22,4 @@ import { + SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -44,2 +27,4 @@ import MenuIcon from '@material-ui/icons/Menu'; + import SearchIcon from '@material-ui/icons/Search'; ++import { MyGroupsSidebarItem } from '@backstage/plugin-org'; ++import GroupIcon from '@material-ui/icons/People'; + +@@ -62,3 +47,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -66,8 +51,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -89,2 +69,7 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + ++ + +@@ -95,3 +80,3 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ {/* Items in this group will be scrollable if they run out of space */} + +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..6164c76 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,7 +27,5 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +-import { +- isGithubActionsAvailable, +- EntityGithubActionsContent, +-} from '@backstage/plugin-github-actions'; + import { +@@ -70,2 +53,18 @@ import { + ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++import { ++ EntityKubernetesContent, ++ isKubernetesAvailable, ++} from '@backstage/plugin-kubernetes'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); ++ + const cicdContent = ( +@@ -74,5 +73,9 @@ const cicdContent = ( + +- +- +- ++ {/* ++ Here you can add support for different CI/CD services, for example ++ using @backstage-community/plugin-github-actions as follows: ++ ++ ++ ++ */} + +@@ -107,2 +110,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -146,2 +157,10 @@ const serviceEntityPage = ( + ++ ++ ++ ++ + +@@ -169,3 +188,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -184,2 +203,10 @@ const websiteEntityPage = ( + ++ ++ ++ ++ + +@@ -196,3 +223,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -215,3 +242,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -296,5 +323,8 @@ const groupPage = ( + +- ++ + + ++ ++ ++ + +@@ -315,3 +345,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/app/src/index.tsx b/packages/app/src/index.tsx +index b16aaf7..d875c77 100644 +--- a/packages/app/src/index.tsx ++++ b/packages/app/src/index.tsx +@@ -2,5 +2,5 @@ import '@backstage/cli/asset-types'; + import React from 'react'; +-import ReactDOM from 'react-dom'; ++import ReactDOM from 'react-dom/client'; + import App from './App'; + +-ReactDOM.render(, document.getElementById('root')); ++ReactDOM.createRoot(document.getElementById('root')!).render(); +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index dd35d4d..bef67b5 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,15 @@ 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 --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++# This will include the examples, if you don't need these simply remove this line ++COPY --chown=node:node examples ./examples + + # 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..6a14abc 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -15,29 +15,34 @@ + "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.24.0", ++ "@backstage/backend-defaults": "^0.4.2", ++ "@backstage/backend-tasks": "^0.6.0", ++ "@backstage/config": "^1.2.0", ++ "@backstage/plugin-app-backend": "^0.3.72", ++ "@backstage/plugin-auth-backend": "^0.22.10", ++ "@backstage/plugin-auth-backend-module-github-provider": "^0.1.20", ++ "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.9", ++ "@backstage/plugin-auth-node": "^0.5.0", ++ "@backstage/plugin-catalog-backend": "^1.25.0", ++ "@backstage/plugin-catalog-backend-module-logs": "^0.0.2", ++ "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.21", ++ "@backstage/plugin-kubernetes-backend": "^0.18.4", ++ "@backstage/plugin-permission-backend": "^0.5.47", ++ "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.1.20", ++ "@backstage/plugin-permission-common": "^0.8.1", ++ "@backstage/plugin-permission-node": "^0.8.1", ++ "@backstage/plugin-proxy-backend": "^0.5.4", ++ "@backstage/plugin-scaffolder-backend": "^1.24.0", ++ "@backstage/plugin-search-backend": "^1.5.15", ++ "@backstage/plugin-search-backend-module-catalog": "^0.2.0", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.33", ++ "@backstage/plugin-search-backend-module-techdocs": "^0.2.0", ++ "@backstage/plugin-search-backend-node": "^1.3.0", ++ "@backstage/plugin-techdocs-backend": "^1.10.10", + "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", +- "dockerode": "^3.3.1", +- "express": "^4.17.1", +- "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "@vscode/sqlite3": "^5.0.7", ++ "better-sqlite3": "^9.0.0", ++ "node-gyp": "^10.0.0", ++ "pg": "^8.11.3", + "winston": "^3.2.1" +@@ -45,6 +50,6 @@ + "devDependencies": { +- "@backstage/cli": "^0.15.2", +- "@types/dockerode": "^3.3.0", ++ "@backstage/cli": "^0.27.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.test.ts b/packages/backend/src/index.test.ts +deleted file mode 100644 +index 7814b8c..0000000 +--- a/packages/backend/src/index.test.ts ++++ /dev/null +@@ -1,8 +0,0 @@ +-import { PluginEnvironment } from './types'; +- +-describe('test', () => { +- it('unbreaks the test runner', () => { +- const unbreaker = {} as PluginEnvironment; +- expect(unbreaker).toBeTruthy(); +- }); +-}); +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 70bc66b..f57d4c5 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -8,102 +8,47 @@ + +-import Router from 'express-promise-router'; +-import { +- createServiceBuilder, +- loadBackendConfig, +- getRootLogger, +- useHotMemoize, +- notFoundHandler, +- CacheManager, +- DatabaseManager, +- SingleHostDiscovery, +- UrlReaders, +- ServerTokenManager, +-} from '@backstage/backend-common'; +-import { TaskScheduler } from '@backstage/backend-tasks'; +-import { Config } from '@backstage/config'; +-import app from './plugins/app'; +-import auth from './plugins/auth'; +-import catalog from './plugins/catalog'; +-import scaffolder from './plugins/scaffolder'; +-import proxy from './plugins/proxy'; +-import techdocs from './plugins/techdocs'; +-import search from './plugins/search'; +-import { PluginEnvironment } from './types'; +-import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { createBackend } from '@backstage/backend-defaults'; + +-function makeCreateEnv(config: Config) { +- const root = getRootLogger(); +- const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); +- const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); +- const permissions = ServerPermissionClient.fromConfig(config, { +- discovery, +- tokenManager, +- }); ++const backend = createBackend(); + +- root.info(`Created UrlReader ${reader}`); ++backend.add(import('@backstage/plugin-app-backend/alpha')); ++backend.add(import('@backstage/plugin-proxy-backend/alpha')); ++backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); ++backend.add(import('@backstage/plugin-techdocs-backend/alpha')); + +- return (plugin: string): PluginEnvironment => { +- const logger = root.child({ type: 'plugin', plugin }); +- const database = databaseManager.forPlugin(plugin); +- const cache = cacheManager.forPlugin(plugin); +- const scheduler = taskScheduler.forPlugin(plugin); +- return { +- logger, +- database, +- cache, +- config, +- reader, +- discovery, +- tokenManager, +- scheduler, +- permissions, +- }; +- }; +-} ++// auth plugin ++backend.add(import('@backstage/plugin-auth-backend')); ++// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin ++backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); ++// See https://backstage.io/docs/auth/guest/provider + +-async function main() { +- const config = await loadBackendConfig({ +- argv: process.argv, +- logger: getRootLogger(), +- }); +- const createEnv = makeCreateEnv(config); ++// catalog plugin ++backend.add(import('@backstage/plugin-catalog-backend/alpha')); ++backend.add( ++ import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ++); + +- const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); +- const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); +- const authEnv = useHotMemoize(module, () => createEnv('auth')); +- const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); +- const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); +- const searchEnv = useHotMemoize(module, () => createEnv('search')); +- const appEnv = useHotMemoize(module, () => createEnv('app')); ++// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors ++backend.add(import('@backstage/plugin-catalog-backend-module-logs')); + +- const apiRouter = Router(); +- apiRouter.use('/catalog', await catalog(catalogEnv)); +- apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); +- apiRouter.use('/auth', await auth(authEnv)); +- apiRouter.use('/techdocs', await techdocs(techdocsEnv)); +- apiRouter.use('/proxy', await proxy(proxyEnv)); +- apiRouter.use('/search', await search(searchEnv)); ++// permission plugin ++backend.add(import('@backstage/plugin-permission-backend/alpha')); ++// See https://backstage.io/docs/permissions/getting-started for how to create your own permission policy ++backend.add( ++ import('@backstage/plugin-permission-backend-module-allow-all-policy'), ++); + +- // Add backends ABOVE this line; this 404 handler is the catch-all fallback +- apiRouter.use(notFoundHandler()); ++// search plugin ++backend.add(import('@backstage/plugin-search-backend/alpha')); + +- const service = createServiceBuilder(module) +- .loadConfig(config) +- .addRouter('/api', apiRouter) +- .addRouter('', await app(appEnv)); ++// search engine ++// See https://backstage.io/docs/features/search/search-engines ++backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); + +- await service.start().catch(err => { +- console.log(err); +- process.exit(1); +- }); +-} ++// search collators ++backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); ++backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); + +-module.hot?.accept(); +-main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); +- process.exit(1); +-}); ++// kubernetes ++backend.add(import('@backstage/plugin-kubernetes-backend/alpha')); ++ ++backend.start(); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +deleted file mode 100644 +index 14e19a1..0000000 +--- a/packages/backend/src/plugins/app.ts ++++ /dev/null +@@ -1,16 +0,0 @@ +-import { createRouter } from '@backstage/plugin-app-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- database, +-}: PluginEnvironment): Promise { +- return await createRouter({ +- logger, +- config, +- database, +- appPackageName: 'app', +- }); +-} +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +deleted file mode 100644 +index 015c864..0000000 +--- a/packages/backend/src/plugins/auth.ts ++++ /dev/null +@@ -1,19 +0,0 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +- tokenManager, +-}: PluginEnvironment): Promise { +- return await createRouter({ +- logger, +- config, +- database, +- discovery, +- tokenManager, +- }); +-} +diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts +deleted file mode 100644 +index 876cb6b..0000000 +--- a/packages/backend/src/plugins/catalog.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +-import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const builder = await CatalogBuilder.create(env); +- builder.addProcessor(new ScaffolderEntitiesProcessor()); +- const { processingEngine, router } = await builder.build(); +- await processingEngine.start(); +- return router; +-} +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +deleted file mode 100644 +index 506f6d9..0000000 +--- a/packages/backend/src/plugins/proxy.ts ++++ /dev/null +@@ -1,11 +0,0 @@ +-import { createRouter } from '@backstage/plugin-proxy-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); +-} +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +deleted file mode 100644 +index a460fd8..0000000 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ /dev/null +@@ -1,21 +0,0 @@ +-import { CatalogClient } from '@backstage/catalog-client'; +-import { createRouter } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import type { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- database, +- reader, +- discovery, +-}: PluginEnvironment): Promise { +- const catalogClient = new CatalogClient({ discoveryApi: discovery }); +- return await createRouter({ +- logger, +- config, +- database, +- catalogClient, +- reader, +- }); +-} +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +deleted file mode 100644 +index c359cb4..0000000 +--- a/packages/backend/src/plugins/search.ts ++++ /dev/null +@@ -1,58 +0,0 @@ +-import { useHotCleanup } from '@backstage/backend-common'; +-import { createRouter } from '@backstage/plugin-search-backend'; +-import { +- IndexBuilder, +- LunrSearchEngine, +-} from '@backstage/plugin-search-backend-node'; +-import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; +- +-export default async function createPlugin({ +- logger, +- permissions, +- discovery, +- config, +- tokenManager, +-}: PluginEnvironment) { +- // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); +- +- // Collators are responsible for gathering documents known to plugins. This +- // collator gathers entities from the software catalog. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- factory: DefaultCatalogCollatorFactory.fromConfig(config, { +- discovery, +- tokenManager, +- }), +- }); +- +- // collator gathers entities from techdocs. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- factory: DefaultTechDocsCollatorFactory.fromConfig(config, { +- discovery, +- logger, +- tokenManager, +- }), +- }); +- +- // The scheduler controls when documents are gathered from collators and sent +- // to the search engine for indexing. +- const { scheduler } = await indexBuilder.build(); +- +- // 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()); +- +- return await createRouter({ +- engine: indexBuilder.getSearchEngine(), +- types: indexBuilder.getDocumentTypes(), +- permissions, +- config, +- logger, +- }); +-} +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +deleted file mode 100644 +index 054c64d..0000000 +--- a/packages/backend/src/plugins/techdocs.ts ++++ /dev/null +@@ -1,55 +0,0 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; +-import { +- createRouter, +- Generators, +- Preparers, +- Publisher, +-} from '@backstage/plugin-techdocs-backend'; +-import Docker from 'dockerode'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +- cache, +-}: PluginEnvironment): Promise { +- // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, +- }); +- +- // Docker client (conditionally) used by the generators, based on techdocs.generators config. +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- +- // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, +- containerRunner, +- }); +- +- // Publisher is used for +- // 1. Publishing generated files to storage +- // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, +- }); +- +- // checks if the publisher is working and logs the result +- await publisher.getReadiness(); +- +- return await createRouter({ +- preparers, +- generators, +- publisher, +- logger, +- config, +- discovery, +- cache, +- }); +-} +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +deleted file mode 100644 +index 0862b0e..0000000 +--- a/packages/backend/src/types.ts ++++ /dev/null +@@ -1,23 +0,0 @@ +-import { Logger } from 'winston'; +-import { Config } from '@backstage/config'; +-import { +- PluginCacheManager, +- PluginDatabaseManager, +- PluginEndpointDiscovery, +- TokenManager, +- UrlReader, +-} from '@backstage/backend-common'; +-import { PluginTaskScheduler } from '@backstage/backend-tasks'; +-import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; +- +-export type PluginEnvironment = { +- logger: Logger; +- database: PluginDatabaseManager; +- cache: PluginCacheManager; +- config: Config; +- reader: UrlReader; +- discovery: PluginEndpointDiscovery; +- tokenManager: TokenManager; +- scheduler: PluginTaskScheduler; +- permissions: PermissionAuthorizer; +-}; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..733be13 +--- /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 dev', ++ 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..c37fc61 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1,3 @@ ++# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. ++# yarn lockfile v1 ++ diff --git a/diffs/0.4.24..0.5.18.diff b/diffs/0.4.24..0.5.18.diff new file mode 100644 index 00000000..dc951ac0 --- /dev/null +++ b/diffs/0.4.24..0.5.18.diff @@ -0,0 +1,1861 @@ +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..5d426f5 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,51 @@ 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/apis/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 ++ ++auth: ++ providers: ++ guest: {} ++ ++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: ++ # Local example data, replace this with your production config, these are intended for demo use only. ++ # File locations are relative to the backend process, typically in a deployed context, such as in a Docker container, this will be the root ++ - type: file ++ target: ./examples/entities.yaml ++ ++ # Local example template ++ - type: file ++ target: ./examples/template/template.yaml ++ rules: ++ - allow: [Template] ++ ++ # Local example organizational data ++ - type: file ++ target: ./examples/org.yaml ++ rules: ++ - allow: [User, Group] +diff --git a/app-config.yaml b/app-config.yaml +index e7732cf..ca52ec5 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/integrations/github/locations#configuration + 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 + +@@ -59,3 +66,5 @@ auth: + # see https://backstage.io/docs/auth/ to learn about auth providers +- providers: {} ++ providers: ++ # See https://backstage.io/docs/auth/guest/provider ++ guest: {} + +@@ -69,36 +78,36 @@ 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 ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml ++ rules: ++ - allow: [Template] + +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml ++ rules: ++ - allow: [User, Group] + +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml + +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.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] + +- # 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 +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/docs-template/template.yaml +- rules: +- - allow: [Template] ++kubernetes: ++ # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options ++ ++# see https://backstage.io/docs/permissions/getting-started for more on the permission framework ++permission: ++ # setting this to `false` will disable permissions ++ enabled: true +diff --git a/backstage.json b/backstage.json +index 1587a66..230d39d 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "1.0.0" ++ "version": "1.30.0" + } +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..80a963e 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.27.0", ++ "@backstage/e2e-test-utils": "^0.1.1", ++ "@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": "^10.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.5.4" ++ "typescript": "~5.4.0" ++ }, ++ "resolutions": { ++ "@types/react": "^18", ++ "@types/react-dom": "^18" + }, +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..839ff88 +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,27 @@ ++/* ++ * 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('/'); ++ ++ const enterButton = page.getByRole('button', { name: 'Enter' }); ++ await expect(enterButton).toBeVisible(); ++ await enterButton.click(); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 0cfef69..346ffc1 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -8,25 +8,34 @@ + }, ++ "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.5.10", ++ "@backstage/catalog-model": "^1.6.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/core-app-api": "^1.14.2", ++ "@backstage/core-components": "^0.14.10", ++ "@backstage/core-plugin-api": "^1.9.3", ++ "@backstage/integration-react": "^1.1.30", ++ "@backstage/plugin-api-docs": "^0.11.8", ++ "@backstage/plugin-catalog": "^1.22.0", ++ "@backstage/plugin-catalog-common": "^1.0.26", ++ "@backstage/plugin-catalog-graph": "^0.4.8", ++ "@backstage/plugin-catalog-import": "^0.12.2", ++ "@backstage/plugin-catalog-react": "^1.12.3", ++ "@backstage/plugin-kubernetes": "^0.11.13", ++ "@backstage/plugin-org": "^0.6.28", ++ "@backstage/plugin-permission-react": "^0.4.25", ++ "@backstage/plugin-scaffolder": "^1.24.0", ++ "@backstage/plugin-search": "^1.4.15", ++ "@backstage/plugin-search-react": "^1.7.14", ++ "@backstage/plugin-techdocs": "^1.10.8", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.13", ++ "@backstage/plugin-techdocs-react": "^1.2.7", ++ "@backstage/plugin-user-settings": "^0.8.11", ++ "@backstage/theme": "^0.5.6", + "@material-ui/core": "^4.12.2", +@@ -34,31 +43,17 @@ + "history": "^5.0.0", +- "react": "^17.0.2", +- "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": "^18.0.2", ++ "react-dom": "^18.0.2", ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^1.0.0", +- "@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", ++ "@backstage/test-utils": "^1.5.10", ++ "@playwright/test": "^1.32.3", ++ "@testing-library/jest-dom": "^6.0.0", ++ "@testing-library/react": "^14.0.0", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^9.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..18da7c4 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -8,5 +8,4 @@ + name="description" +- content="Backstage is an open platform for building developer portals" ++ content="Backstage is an open source framework for building developer portals" + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index b94cac7..ec8ba1d 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { renderWithEffects } from '@backstage/test-utils'; ++import { render, waitFor } from '@testing-library/react'; + import App from './App'; +@@ -22,4 +22,7 @@ describe('App', () => { + +- const rendered = await renderWithEffects(); +- expect(rendered.baseElement).toBeInTheDocument(); ++ const rendered = render(); ++ ++ await waitFor(() => { ++ expect(rendered.baseElement).toBeInTheDocument(); ++ }); + }); +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index f4ff424..e24c468 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'; +@@ -15,3 +15,2 @@ import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +-import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +@@ -21,2 +20,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'; +@@ -27,7 +28,11 @@ import { Root } from './components/Root'; + +-import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; ++import { ++ AlertDisplay, ++ OAuthRequestDialog, ++ SignInPage, ++} 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 +45,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -46,2 +52,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -51,10 +58,10 @@ const app = createApp({ + }, ++ components: { ++ SignInPage: props => , ++ }, + }); + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -70,3 +77,7 @@ const routes = ( + element={} +- /> ++ > ++ ++ ++ ++ + } /> +@@ -74,9 +85,8 @@ const routes = ( + } +- /> +- } ++ element={ ++ ++ ++ ++ } + /> +@@ -90,4 +100,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -97,5 +107,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..853d175 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,22 +1,5 @@ +-/* +- * 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'; + import ExtensionIcon from '@material-ui/icons/Extension'; +-import MapIcon from '@material-ui/icons/MyLocation'; + import LibraryBooks from '@material-ui/icons/LibraryBooks'; +@@ -25,3 +8,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -34,3 +16,2 @@ import { + sidebarConfig, +- SidebarContext, + SidebarDivider, +@@ -41,2 +22,4 @@ import { + SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -44,2 +27,4 @@ import MenuIcon from '@material-ui/icons/Menu'; + import SearchIcon from '@material-ui/icons/Search'; ++import { MyGroupsSidebarItem } from '@backstage/plugin-org'; ++import GroupIcon from '@material-ui/icons/People'; + +@@ -62,3 +47,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -66,8 +51,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -89,2 +69,7 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + ++ + +@@ -95,3 +80,3 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ {/* Items in this group will be scrollable if they run out of space */} + +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..6164c76 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,7 +27,5 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +-import { +- isGithubActionsAvailable, +- EntityGithubActionsContent, +-} from '@backstage/plugin-github-actions'; + import { +@@ -70,2 +53,18 @@ import { + ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++import { ++ EntityKubernetesContent, ++ isKubernetesAvailable, ++} from '@backstage/plugin-kubernetes'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); ++ + const cicdContent = ( +@@ -74,5 +73,9 @@ const cicdContent = ( + +- +- +- ++ {/* ++ Here you can add support for different CI/CD services, for example ++ using @backstage-community/plugin-github-actions as follows: ++ ++ ++ ++ */} + +@@ -107,2 +110,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -146,2 +157,10 @@ const serviceEntityPage = ( + ++ ++ ++ ++ + +@@ -169,3 +188,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -184,2 +203,10 @@ const websiteEntityPage = ( + ++ ++ ++ ++ + +@@ -196,3 +223,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -215,3 +242,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -296,5 +323,8 @@ const groupPage = ( + +- ++ + + ++ ++ ++ + +@@ -315,3 +345,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/app/src/index.tsx b/packages/app/src/index.tsx +index b16aaf7..d875c77 100644 +--- a/packages/app/src/index.tsx ++++ b/packages/app/src/index.tsx +@@ -2,5 +2,5 @@ import '@backstage/cli/asset-types'; + import React from 'react'; +-import ReactDOM from 'react-dom'; ++import ReactDOM from 'react-dom/client'; + import App from './App'; + +-ReactDOM.render(, document.getElementById('root')); ++ReactDOM.createRoot(document.getElementById('root')!).render(); +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index dd35d4d..bef67b5 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,15 @@ 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 --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++# This will include the examples, if you don't need these simply remove this line ++COPY --chown=node:node examples ./examples + + # 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..6a14abc 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -15,29 +15,34 @@ + "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.24.0", ++ "@backstage/backend-defaults": "^0.4.2", ++ "@backstage/backend-tasks": "^0.6.0", ++ "@backstage/config": "^1.2.0", ++ "@backstage/plugin-app-backend": "^0.3.72", ++ "@backstage/plugin-auth-backend": "^0.22.10", ++ "@backstage/plugin-auth-backend-module-github-provider": "^0.1.20", ++ "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.9", ++ "@backstage/plugin-auth-node": "^0.5.0", ++ "@backstage/plugin-catalog-backend": "^1.25.0", ++ "@backstage/plugin-catalog-backend-module-logs": "^0.0.2", ++ "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.21", ++ "@backstage/plugin-kubernetes-backend": "^0.18.4", ++ "@backstage/plugin-permission-backend": "^0.5.47", ++ "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.1.20", ++ "@backstage/plugin-permission-common": "^0.8.1", ++ "@backstage/plugin-permission-node": "^0.8.1", ++ "@backstage/plugin-proxy-backend": "^0.5.4", ++ "@backstage/plugin-scaffolder-backend": "^1.24.0", ++ "@backstage/plugin-search-backend": "^1.5.15", ++ "@backstage/plugin-search-backend-module-catalog": "^0.2.0", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.33", ++ "@backstage/plugin-search-backend-module-techdocs": "^0.2.0", ++ "@backstage/plugin-search-backend-node": "^1.3.0", ++ "@backstage/plugin-techdocs-backend": "^1.10.10", + "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", +- "dockerode": "^3.3.1", +- "express": "^4.17.1", +- "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "better-sqlite3": "^7.5.0", ++ "better-sqlite3": "^9.0.0", ++ "node-gyp": "^10.0.0", ++ "pg": "^8.11.3", + "winston": "^3.2.1" +@@ -45,6 +50,6 @@ + "devDependencies": { +- "@backstage/cli": "^0.16.0", +- "@types/dockerode": "^3.3.0", ++ "@backstage/cli": "^0.27.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.test.ts b/packages/backend/src/index.test.ts +deleted file mode 100644 +index 7814b8c..0000000 +--- a/packages/backend/src/index.test.ts ++++ /dev/null +@@ -1,8 +0,0 @@ +-import { PluginEnvironment } from './types'; +- +-describe('test', () => { +- it('unbreaks the test runner', () => { +- const unbreaker = {} as PluginEnvironment; +- expect(unbreaker).toBeTruthy(); +- }); +-}); +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 70bc66b..f57d4c5 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -8,102 +8,47 @@ + +-import Router from 'express-promise-router'; +-import { +- createServiceBuilder, +- loadBackendConfig, +- getRootLogger, +- useHotMemoize, +- notFoundHandler, +- CacheManager, +- DatabaseManager, +- SingleHostDiscovery, +- UrlReaders, +- ServerTokenManager, +-} from '@backstage/backend-common'; +-import { TaskScheduler } from '@backstage/backend-tasks'; +-import { Config } from '@backstage/config'; +-import app from './plugins/app'; +-import auth from './plugins/auth'; +-import catalog from './plugins/catalog'; +-import scaffolder from './plugins/scaffolder'; +-import proxy from './plugins/proxy'; +-import techdocs from './plugins/techdocs'; +-import search from './plugins/search'; +-import { PluginEnvironment } from './types'; +-import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { createBackend } from '@backstage/backend-defaults'; + +-function makeCreateEnv(config: Config) { +- const root = getRootLogger(); +- const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); +- const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); +- const permissions = ServerPermissionClient.fromConfig(config, { +- discovery, +- tokenManager, +- }); ++const backend = createBackend(); + +- root.info(`Created UrlReader ${reader}`); ++backend.add(import('@backstage/plugin-app-backend/alpha')); ++backend.add(import('@backstage/plugin-proxy-backend/alpha')); ++backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); ++backend.add(import('@backstage/plugin-techdocs-backend/alpha')); + +- return (plugin: string): PluginEnvironment => { +- const logger = root.child({ type: 'plugin', plugin }); +- const database = databaseManager.forPlugin(plugin); +- const cache = cacheManager.forPlugin(plugin); +- const scheduler = taskScheduler.forPlugin(plugin); +- return { +- logger, +- database, +- cache, +- config, +- reader, +- discovery, +- tokenManager, +- scheduler, +- permissions, +- }; +- }; +-} ++// auth plugin ++backend.add(import('@backstage/plugin-auth-backend')); ++// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin ++backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); ++// See https://backstage.io/docs/auth/guest/provider + +-async function main() { +- const config = await loadBackendConfig({ +- argv: process.argv, +- logger: getRootLogger(), +- }); +- const createEnv = makeCreateEnv(config); ++// catalog plugin ++backend.add(import('@backstage/plugin-catalog-backend/alpha')); ++backend.add( ++ import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ++); + +- const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); +- const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); +- const authEnv = useHotMemoize(module, () => createEnv('auth')); +- const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); +- const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); +- const searchEnv = useHotMemoize(module, () => createEnv('search')); +- const appEnv = useHotMemoize(module, () => createEnv('app')); ++// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors ++backend.add(import('@backstage/plugin-catalog-backend-module-logs')); + +- const apiRouter = Router(); +- apiRouter.use('/catalog', await catalog(catalogEnv)); +- apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); +- apiRouter.use('/auth', await auth(authEnv)); +- apiRouter.use('/techdocs', await techdocs(techdocsEnv)); +- apiRouter.use('/proxy', await proxy(proxyEnv)); +- apiRouter.use('/search', await search(searchEnv)); ++// permission plugin ++backend.add(import('@backstage/plugin-permission-backend/alpha')); ++// See https://backstage.io/docs/permissions/getting-started for how to create your own permission policy ++backend.add( ++ import('@backstage/plugin-permission-backend-module-allow-all-policy'), ++); + +- // Add backends ABOVE this line; this 404 handler is the catch-all fallback +- apiRouter.use(notFoundHandler()); ++// search plugin ++backend.add(import('@backstage/plugin-search-backend/alpha')); + +- const service = createServiceBuilder(module) +- .loadConfig(config) +- .addRouter('/api', apiRouter) +- .addRouter('', await app(appEnv)); ++// search engine ++// See https://backstage.io/docs/features/search/search-engines ++backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); + +- await service.start().catch(err => { +- console.log(err); +- process.exit(1); +- }); +-} ++// search collators ++backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); ++backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); + +-module.hot?.accept(); +-main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); +- process.exit(1); +-}); ++// kubernetes ++backend.add(import('@backstage/plugin-kubernetes-backend/alpha')); ++ ++backend.start(); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +deleted file mode 100644 +index 7c37f68..0000000 +--- a/packages/backend/src/plugins/app.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { createRouter } from '@backstage/plugin-app-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- return await createRouter({ +- 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 +deleted file mode 100644 +index 1476e66..0000000 +--- a/packages/backend/src/plugins/auth.ts ++++ /dev/null +@@ -1,15 +0,0 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-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, +- }); +-} +diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts +deleted file mode 100644 +index 876cb6b..0000000 +--- a/packages/backend/src/plugins/catalog.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +-import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const builder = await CatalogBuilder.create(env); +- builder.addProcessor(new ScaffolderEntitiesProcessor()); +- const { processingEngine, router } = await builder.build(); +- await processingEngine.start(); +- return router; +-} +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +deleted file mode 100644 +index 54ec393..0000000 +--- a/packages/backend/src/plugins/proxy.ts ++++ /dev/null +@@ -1,13 +0,0 @@ +-import { createRouter } from '@backstage/plugin-proxy-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-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 +deleted file mode 100644 +index 7ce5fcf..0000000 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ /dev/null +@@ -1,20 +0,0 @@ +-import { CatalogClient } from '@backstage/catalog-client'; +-import { createRouter } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import type { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const catalogClient = new CatalogClient({ +- discoveryApi: env.discovery, +- }); +- +- return await createRouter({ +- logger: env.logger, +- config: env.config, +- database: env.database, +- reader: env.reader, +- catalogClient, +- }); +-} +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +deleted file mode 100644 +index 8df6b0a..0000000 +--- a/packages/backend/src/plugins/search.ts ++++ /dev/null +@@ -1,60 +0,0 @@ +-import { useHotCleanup } from '@backstage/backend-common'; +-import { createRouter } from '@backstage/plugin-search-backend'; +-import { +- IndexBuilder, +- LunrSearchEngine, +-} from '@backstage/plugin-search-backend-node'; +-import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; +-import { Router } from 'express'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ +- logger: env.logger, +- }); +- const indexBuilder = new IndexBuilder({ +- logger: env.logger, +- searchEngine, +- }); +- +- // Collators are responsible for gathering documents known to plugins. This +- // collator gathers entities from the software catalog. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- factory: DefaultCatalogCollatorFactory.fromConfig(env.config, { +- discovery: env.discovery, +- tokenManager: env.tokenManager, +- }), +- }); +- +- // collator gathers entities from techdocs. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { +- discovery: env.discovery, +- logger: env.logger, +- tokenManager: env.tokenManager, +- }), +- }); +- +- // The scheduler controls when documents are gathered from collators and sent +- // to the search engine for indexing. +- const { scheduler } = await indexBuilder.build(); +- +- // 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()); +- +- return await createRouter({ +- engine: indexBuilder.getSearchEngine(), +- 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 +deleted file mode 100644 +index be8bb0c..0000000 +--- a/packages/backend/src/plugins/techdocs.ts ++++ /dev/null +@@ -1,51 +0,0 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; +-import { +- createRouter, +- Generators, +- Preparers, +- Publisher, +-} from '@backstage/plugin-techdocs-backend'; +-import Docker from 'dockerode'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(env.config, { +- logger: env.logger, +- reader: env.reader, +- }); +- +- // Docker client (conditionally) used by the generators, based on techdocs.generators config. +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- +- // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(env.config, { +- logger: env.logger, +- containerRunner, +- }); +- +- // Publisher is used for +- // 1. Publishing generated files to storage +- // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(env.config, { +- logger: env.logger, +- discovery: env.discovery, +- }); +- +- // checks if the publisher is working and logs the result +- await publisher.getReadiness(); +- +- return await createRouter({ +- preparers, +- generators, +- publisher, +- 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 +deleted file mode 100644 +index 0862b0e..0000000 +--- a/packages/backend/src/types.ts ++++ /dev/null +@@ -1,23 +0,0 @@ +-import { Logger } from 'winston'; +-import { Config } from '@backstage/config'; +-import { +- PluginCacheManager, +- PluginDatabaseManager, +- PluginEndpointDiscovery, +- TokenManager, +- UrlReader, +-} from '@backstage/backend-common'; +-import { PluginTaskScheduler } from '@backstage/backend-tasks'; +-import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; +- +-export type PluginEnvironment = { +- logger: Logger; +- database: PluginDatabaseManager; +- cache: PluginCacheManager; +- config: Config; +- reader: UrlReader; +- discovery: PluginEndpointDiscovery; +- tokenManager: TokenManager; +- scheduler: PluginTaskScheduler; +- permissions: PermissionAuthorizer; +-}; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..733be13 +--- /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 dev', ++ 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..c37fc61 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1,3 @@ ++# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. ++# yarn lockfile v1 ++ diff --git a/diffs/0.4.25..0.5.18.diff b/diffs/0.4.25..0.5.18.diff new file mode 100644 index 00000000..bfe9722b --- /dev/null +++ b/diffs/0.4.25..0.5.18.diff @@ -0,0 +1,1863 @@ +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..5d426f5 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,51 @@ 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/apis/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 ++ ++auth: ++ providers: ++ guest: {} ++ ++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: ++ # Local example data, replace this with your production config, these are intended for demo use only. ++ # File locations are relative to the backend process, typically in a deployed context, such as in a Docker container, this will be the root ++ - type: file ++ target: ./examples/entities.yaml ++ ++ # Local example template ++ - type: file ++ target: ./examples/template/template.yaml ++ rules: ++ - allow: [Template] ++ ++ # Local example organizational data ++ - type: file ++ target: ./examples/org.yaml ++ rules: ++ - allow: [User, Group] +diff --git a/app-config.yaml b/app-config.yaml +index e7732cf..ca52ec5 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/integrations/github/locations#configuration + 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 + +@@ -59,3 +66,5 @@ auth: + # see https://backstage.io/docs/auth/ to learn about auth providers +- providers: {} ++ providers: ++ # See https://backstage.io/docs/auth/guest/provider ++ guest: {} + +@@ -69,36 +78,36 @@ 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 ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml ++ rules: ++ - allow: [Template] + +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml ++ rules: ++ - allow: [User, Group] + +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml + +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.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] + +- # 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 +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/docs-template/template.yaml +- rules: +- - allow: [Template] ++kubernetes: ++ # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options ++ ++# see https://backstage.io/docs/permissions/getting-started for more on the permission framework ++permission: ++ # setting this to `false` will disable permissions ++ enabled: true +diff --git a/backstage.json b/backstage.json +index bbea0d7..230d39d 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "1.0.3" ++ "version": "1.30.0" + } +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..80a963e 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,12 +33,15 @@ + "devDependencies": { +- "@backstage/cli": "^0.16.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/e2e-test-utils": "^0.1.1", ++ "@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": "^10.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.5.4" ++ "typescript": "~5.4.0" + }, + "resolutions": { +- "@types/react": "^17", +- "@types/react-dom": "^17" ++ "@types/react": "^18", ++ "@types/react-dom": "^18" + }, +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..839ff88 +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,27 @@ ++/* ++ * 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('/'); ++ ++ const enterButton = page.getByRole('button', { name: 'Enter' }); ++ await expect(enterButton).toBeVisible(); ++ await enterButton.click(); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index cfe2065..346ffc1 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -8,25 +8,34 @@ + }, ++ "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.5.10", ++ "@backstage/catalog-model": "^1.6.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/core-app-api": "^1.14.2", ++ "@backstage/core-components": "^0.14.10", ++ "@backstage/core-plugin-api": "^1.9.3", ++ "@backstage/integration-react": "^1.1.30", ++ "@backstage/plugin-api-docs": "^0.11.8", ++ "@backstage/plugin-catalog": "^1.22.0", ++ "@backstage/plugin-catalog-common": "^1.0.26", ++ "@backstage/plugin-catalog-graph": "^0.4.8", ++ "@backstage/plugin-catalog-import": "^0.12.2", ++ "@backstage/plugin-catalog-react": "^1.12.3", ++ "@backstage/plugin-kubernetes": "^0.11.13", ++ "@backstage/plugin-org": "^0.6.28", ++ "@backstage/plugin-permission-react": "^0.4.25", ++ "@backstage/plugin-scaffolder": "^1.24.0", ++ "@backstage/plugin-search": "^1.4.15", ++ "@backstage/plugin-search-react": "^1.7.14", ++ "@backstage/plugin-techdocs": "^1.10.8", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.13", ++ "@backstage/plugin-techdocs-react": "^1.2.7", ++ "@backstage/plugin-user-settings": "^0.8.11", ++ "@backstage/theme": "^0.5.6", + "@material-ui/core": "^4.12.2", +@@ -34,31 +43,17 @@ + "history": "^5.0.0", +- "react": "^17.0.2", +- "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": "^18.0.2", ++ "react-dom": "^18.0.2", ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^1.0.0", +- "@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", ++ "@backstage/test-utils": "^1.5.10", ++ "@playwright/test": "^1.32.3", ++ "@testing-library/jest-dom": "^6.0.0", ++ "@testing-library/react": "^14.0.0", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^9.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..18da7c4 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -8,5 +8,4 @@ + name="description" +- content="Backstage is an open platform for building developer portals" ++ content="Backstage is an open source framework for building developer portals" + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index b94cac7..ec8ba1d 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { renderWithEffects } from '@backstage/test-utils'; ++import { render, waitFor } from '@testing-library/react'; + import App from './App'; +@@ -22,4 +22,7 @@ describe('App', () => { + +- const rendered = await renderWithEffects(); +- expect(rendered.baseElement).toBeInTheDocument(); ++ const rendered = render(); ++ ++ await waitFor(() => { ++ expect(rendered.baseElement).toBeInTheDocument(); ++ }); + }); +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index f4ff424..e24c468 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'; +@@ -15,3 +15,2 @@ import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +-import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +@@ -21,2 +20,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'; +@@ -27,7 +28,11 @@ import { Root } from './components/Root'; + +-import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; ++import { ++ AlertDisplay, ++ OAuthRequestDialog, ++ SignInPage, ++} 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 +45,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -46,2 +52,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -51,10 +58,10 @@ const app = createApp({ + }, ++ components: { ++ SignInPage: props => , ++ }, + }); + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -70,3 +77,7 @@ const routes = ( + element={} +- /> ++ > ++ ++ ++ ++ + } /> +@@ -74,9 +85,8 @@ const routes = ( + } +- /> +- } ++ element={ ++ ++ ++ ++ } + /> +@@ -90,4 +100,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -97,5 +107,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..853d175 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,22 +1,5 @@ +-/* +- * 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'; + import ExtensionIcon from '@material-ui/icons/Extension'; +-import MapIcon from '@material-ui/icons/MyLocation'; + import LibraryBooks from '@material-ui/icons/LibraryBooks'; +@@ -25,3 +8,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -34,3 +16,2 @@ import { + sidebarConfig, +- SidebarContext, + SidebarDivider, +@@ -41,2 +22,4 @@ import { + SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -44,2 +27,4 @@ import MenuIcon from '@material-ui/icons/Menu'; + import SearchIcon from '@material-ui/icons/Search'; ++import { MyGroupsSidebarItem } from '@backstage/plugin-org'; ++import GroupIcon from '@material-ui/icons/People'; + +@@ -62,3 +47,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -66,8 +51,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -89,2 +69,7 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + ++ + +@@ -95,3 +80,3 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ {/* Items in this group will be scrollable if they run out of space */} + +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..6164c76 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,7 +27,5 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +-import { +- isGithubActionsAvailable, +- EntityGithubActionsContent, +-} from '@backstage/plugin-github-actions'; + import { +@@ -70,2 +53,18 @@ import { + ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++import { ++ EntityKubernetesContent, ++ isKubernetesAvailable, ++} from '@backstage/plugin-kubernetes'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); ++ + const cicdContent = ( +@@ -74,5 +73,9 @@ const cicdContent = ( + +- +- +- ++ {/* ++ Here you can add support for different CI/CD services, for example ++ using @backstage-community/plugin-github-actions as follows: ++ ++ ++ ++ */} + +@@ -107,2 +110,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -146,2 +157,10 @@ const serviceEntityPage = ( + ++ ++ ++ ++ + +@@ -169,3 +188,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -184,2 +203,10 @@ const websiteEntityPage = ( + ++ ++ ++ ++ + +@@ -196,3 +223,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -215,3 +242,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -296,5 +323,8 @@ const groupPage = ( + +- ++ + + ++ ++ ++ + +@@ -315,3 +345,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/app/src/index.tsx b/packages/app/src/index.tsx +index b16aaf7..d875c77 100644 +--- a/packages/app/src/index.tsx ++++ b/packages/app/src/index.tsx +@@ -2,5 +2,5 @@ import '@backstage/cli/asset-types'; + import React from 'react'; +-import ReactDOM from 'react-dom'; ++import ReactDOM from 'react-dom/client'; + import App from './App'; + +-ReactDOM.render(, document.getElementById('root')); ++ReactDOM.createRoot(document.getElementById('root')!).render(); +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index dd35d4d..bef67b5 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,15 @@ 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 --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++# This will include the examples, if you don't need these simply remove this line ++COPY --chown=node:node examples ./examples + + # 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..6a14abc 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -15,29 +15,34 @@ + "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.24.0", ++ "@backstage/backend-defaults": "^0.4.2", ++ "@backstage/backend-tasks": "^0.6.0", ++ "@backstage/config": "^1.2.0", ++ "@backstage/plugin-app-backend": "^0.3.72", ++ "@backstage/plugin-auth-backend": "^0.22.10", ++ "@backstage/plugin-auth-backend-module-github-provider": "^0.1.20", ++ "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.9", ++ "@backstage/plugin-auth-node": "^0.5.0", ++ "@backstage/plugin-catalog-backend": "^1.25.0", ++ "@backstage/plugin-catalog-backend-module-logs": "^0.0.2", ++ "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.21", ++ "@backstage/plugin-kubernetes-backend": "^0.18.4", ++ "@backstage/plugin-permission-backend": "^0.5.47", ++ "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.1.20", ++ "@backstage/plugin-permission-common": "^0.8.1", ++ "@backstage/plugin-permission-node": "^0.8.1", ++ "@backstage/plugin-proxy-backend": "^0.5.4", ++ "@backstage/plugin-scaffolder-backend": "^1.24.0", ++ "@backstage/plugin-search-backend": "^1.5.15", ++ "@backstage/plugin-search-backend-module-catalog": "^0.2.0", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.33", ++ "@backstage/plugin-search-backend-module-techdocs": "^0.2.0", ++ "@backstage/plugin-search-backend-node": "^1.3.0", ++ "@backstage/plugin-techdocs-backend": "^1.10.10", + "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", +- "dockerode": "^3.3.1", +- "express": "^4.17.1", +- "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "better-sqlite3": "^7.5.0", ++ "better-sqlite3": "^9.0.0", ++ "node-gyp": "^10.0.0", ++ "pg": "^8.11.3", + "winston": "^3.2.1" +@@ -45,6 +50,6 @@ + "devDependencies": { +- "@backstage/cli": "^0.16.0", +- "@types/dockerode": "^3.3.0", ++ "@backstage/cli": "^0.27.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.test.ts b/packages/backend/src/index.test.ts +deleted file mode 100644 +index 7814b8c..0000000 +--- a/packages/backend/src/index.test.ts ++++ /dev/null +@@ -1,8 +0,0 @@ +-import { PluginEnvironment } from './types'; +- +-describe('test', () => { +- it('unbreaks the test runner', () => { +- const unbreaker = {} as PluginEnvironment; +- expect(unbreaker).toBeTruthy(); +- }); +-}); +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 70bc66b..f57d4c5 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -8,102 +8,47 @@ + +-import Router from 'express-promise-router'; +-import { +- createServiceBuilder, +- loadBackendConfig, +- getRootLogger, +- useHotMemoize, +- notFoundHandler, +- CacheManager, +- DatabaseManager, +- SingleHostDiscovery, +- UrlReaders, +- ServerTokenManager, +-} from '@backstage/backend-common'; +-import { TaskScheduler } from '@backstage/backend-tasks'; +-import { Config } from '@backstage/config'; +-import app from './plugins/app'; +-import auth from './plugins/auth'; +-import catalog from './plugins/catalog'; +-import scaffolder from './plugins/scaffolder'; +-import proxy from './plugins/proxy'; +-import techdocs from './plugins/techdocs'; +-import search from './plugins/search'; +-import { PluginEnvironment } from './types'; +-import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { createBackend } from '@backstage/backend-defaults'; + +-function makeCreateEnv(config: Config) { +- const root = getRootLogger(); +- const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); +- const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); +- const permissions = ServerPermissionClient.fromConfig(config, { +- discovery, +- tokenManager, +- }); ++const backend = createBackend(); + +- root.info(`Created UrlReader ${reader}`); ++backend.add(import('@backstage/plugin-app-backend/alpha')); ++backend.add(import('@backstage/plugin-proxy-backend/alpha')); ++backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); ++backend.add(import('@backstage/plugin-techdocs-backend/alpha')); + +- return (plugin: string): PluginEnvironment => { +- const logger = root.child({ type: 'plugin', plugin }); +- const database = databaseManager.forPlugin(plugin); +- const cache = cacheManager.forPlugin(plugin); +- const scheduler = taskScheduler.forPlugin(plugin); +- return { +- logger, +- database, +- cache, +- config, +- reader, +- discovery, +- tokenManager, +- scheduler, +- permissions, +- }; +- }; +-} ++// auth plugin ++backend.add(import('@backstage/plugin-auth-backend')); ++// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin ++backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); ++// See https://backstage.io/docs/auth/guest/provider + +-async function main() { +- const config = await loadBackendConfig({ +- argv: process.argv, +- logger: getRootLogger(), +- }); +- const createEnv = makeCreateEnv(config); ++// catalog plugin ++backend.add(import('@backstage/plugin-catalog-backend/alpha')); ++backend.add( ++ import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ++); + +- const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); +- const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); +- const authEnv = useHotMemoize(module, () => createEnv('auth')); +- const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); +- const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); +- const searchEnv = useHotMemoize(module, () => createEnv('search')); +- const appEnv = useHotMemoize(module, () => createEnv('app')); ++// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors ++backend.add(import('@backstage/plugin-catalog-backend-module-logs')); + +- const apiRouter = Router(); +- apiRouter.use('/catalog', await catalog(catalogEnv)); +- apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); +- apiRouter.use('/auth', await auth(authEnv)); +- apiRouter.use('/techdocs', await techdocs(techdocsEnv)); +- apiRouter.use('/proxy', await proxy(proxyEnv)); +- apiRouter.use('/search', await search(searchEnv)); ++// permission plugin ++backend.add(import('@backstage/plugin-permission-backend/alpha')); ++// See https://backstage.io/docs/permissions/getting-started for how to create your own permission policy ++backend.add( ++ import('@backstage/plugin-permission-backend-module-allow-all-policy'), ++); + +- // Add backends ABOVE this line; this 404 handler is the catch-all fallback +- apiRouter.use(notFoundHandler()); ++// search plugin ++backend.add(import('@backstage/plugin-search-backend/alpha')); + +- const service = createServiceBuilder(module) +- .loadConfig(config) +- .addRouter('/api', apiRouter) +- .addRouter('', await app(appEnv)); ++// search engine ++// See https://backstage.io/docs/features/search/search-engines ++backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); + +- await service.start().catch(err => { +- console.log(err); +- process.exit(1); +- }); +-} ++// search collators ++backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); ++backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); + +-module.hot?.accept(); +-main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); +- process.exit(1); +-}); ++// kubernetes ++backend.add(import('@backstage/plugin-kubernetes-backend/alpha')); ++ ++backend.start(); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +deleted file mode 100644 +index 7c37f68..0000000 +--- a/packages/backend/src/plugins/app.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { createRouter } from '@backstage/plugin-app-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- return await createRouter({ +- 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 +deleted file mode 100644 +index 1476e66..0000000 +--- a/packages/backend/src/plugins/auth.ts ++++ /dev/null +@@ -1,15 +0,0 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-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, +- }); +-} +diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts +deleted file mode 100644 +index 876cb6b..0000000 +--- a/packages/backend/src/plugins/catalog.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +-import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const builder = await CatalogBuilder.create(env); +- builder.addProcessor(new ScaffolderEntitiesProcessor()); +- const { processingEngine, router } = await builder.build(); +- await processingEngine.start(); +- return router; +-} +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +deleted file mode 100644 +index 54ec393..0000000 +--- a/packages/backend/src/plugins/proxy.ts ++++ /dev/null +@@ -1,13 +0,0 @@ +-import { createRouter } from '@backstage/plugin-proxy-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-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 +deleted file mode 100644 +index 7ce5fcf..0000000 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ /dev/null +@@ -1,20 +0,0 @@ +-import { CatalogClient } from '@backstage/catalog-client'; +-import { createRouter } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import type { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const catalogClient = new CatalogClient({ +- discoveryApi: env.discovery, +- }); +- +- return await createRouter({ +- logger: env.logger, +- config: env.config, +- database: env.database, +- reader: env.reader, +- catalogClient, +- }); +-} +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +deleted file mode 100644 +index 8df6b0a..0000000 +--- a/packages/backend/src/plugins/search.ts ++++ /dev/null +@@ -1,60 +0,0 @@ +-import { useHotCleanup } from '@backstage/backend-common'; +-import { createRouter } from '@backstage/plugin-search-backend'; +-import { +- IndexBuilder, +- LunrSearchEngine, +-} from '@backstage/plugin-search-backend-node'; +-import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; +-import { Router } from 'express'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ +- logger: env.logger, +- }); +- const indexBuilder = new IndexBuilder({ +- logger: env.logger, +- searchEngine, +- }); +- +- // Collators are responsible for gathering documents known to plugins. This +- // collator gathers entities from the software catalog. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- factory: DefaultCatalogCollatorFactory.fromConfig(env.config, { +- discovery: env.discovery, +- tokenManager: env.tokenManager, +- }), +- }); +- +- // collator gathers entities from techdocs. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { +- discovery: env.discovery, +- logger: env.logger, +- tokenManager: env.tokenManager, +- }), +- }); +- +- // The scheduler controls when documents are gathered from collators and sent +- // to the search engine for indexing. +- const { scheduler } = await indexBuilder.build(); +- +- // 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()); +- +- return await createRouter({ +- engine: indexBuilder.getSearchEngine(), +- 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 +deleted file mode 100644 +index be8bb0c..0000000 +--- a/packages/backend/src/plugins/techdocs.ts ++++ /dev/null +@@ -1,51 +0,0 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; +-import { +- createRouter, +- Generators, +- Preparers, +- Publisher, +-} from '@backstage/plugin-techdocs-backend'; +-import Docker from 'dockerode'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(env.config, { +- logger: env.logger, +- reader: env.reader, +- }); +- +- // Docker client (conditionally) used by the generators, based on techdocs.generators config. +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- +- // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(env.config, { +- logger: env.logger, +- containerRunner, +- }); +- +- // Publisher is used for +- // 1. Publishing generated files to storage +- // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(env.config, { +- logger: env.logger, +- discovery: env.discovery, +- }); +- +- // checks if the publisher is working and logs the result +- await publisher.getReadiness(); +- +- return await createRouter({ +- preparers, +- generators, +- publisher, +- 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 +deleted file mode 100644 +index 0862b0e..0000000 +--- a/packages/backend/src/types.ts ++++ /dev/null +@@ -1,23 +0,0 @@ +-import { Logger } from 'winston'; +-import { Config } from '@backstage/config'; +-import { +- PluginCacheManager, +- PluginDatabaseManager, +- PluginEndpointDiscovery, +- TokenManager, +- UrlReader, +-} from '@backstage/backend-common'; +-import { PluginTaskScheduler } from '@backstage/backend-tasks'; +-import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; +- +-export type PluginEnvironment = { +- logger: Logger; +- database: PluginDatabaseManager; +- cache: PluginCacheManager; +- config: Config; +- reader: UrlReader; +- discovery: PluginEndpointDiscovery; +- tokenManager: TokenManager; +- scheduler: PluginTaskScheduler; +- permissions: PermissionAuthorizer; +-}; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..733be13 +--- /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 dev', ++ 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..c37fc61 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1,3 @@ ++# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. ++# yarn lockfile v1 ++ diff --git a/diffs/0.4.26..0.5.18.diff b/diffs/0.4.26..0.5.18.diff new file mode 100644 index 00000000..b268d174 --- /dev/null +++ b/diffs/0.4.26..0.5.18.diff @@ -0,0 +1,1837 @@ +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..5d426f5 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,51 @@ 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/apis/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 ++ ++auth: ++ providers: ++ guest: {} ++ ++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: ++ # Local example data, replace this with your production config, these are intended for demo use only. ++ # File locations are relative to the backend process, typically in a deployed context, such as in a Docker container, this will be the root ++ - type: file ++ target: ./examples/entities.yaml ++ ++ # Local example template ++ - type: file ++ target: ./examples/template/template.yaml ++ rules: ++ - allow: [Template] ++ ++ # Local example organizational data ++ - type: file ++ target: ./examples/org.yaml ++ rules: ++ - allow: [User, Group] +diff --git a/app-config.yaml b/app-config.yaml +index 2f2a14b..ca52ec5 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/integrations/github/locations#configuration + 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 + +@@ -59,3 +66,5 @@ auth: + # see https://backstage.io/docs/auth/ to learn about auth providers +- providers: {} ++ providers: ++ # See https://backstage.io/docs/auth/guest/provider ++ guest: {} + +@@ -71,34 +80,34 @@ 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 ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml ++ rules: ++ - allow: [Template] + +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml ++ rules: ++ - allow: [User, Group] + +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml + +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.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] + +- # 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 +- rules: +- - allow: [Template] +- - type: url +- target: https://github.com/backstage/software-templates/blob/main/scaffolder-templates/docs-template/template.yaml +- rules: +- - allow: [Template] ++kubernetes: ++ # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options ++ ++# see https://backstage.io/docs/permissions/getting-started for more on the permission framework ++permission: ++ # setting this to `false` will disable permissions ++ enabled: true +diff --git a/backstage.json b/backstage.json +index fabbd9a..230d39d 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "1.1.0" ++ "version": "1.30.0" + } +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..80a963e 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,12 +33,15 @@ + "devDependencies": { +- "@backstage/cli": "^0.17.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/e2e-test-utils": "^0.1.1", ++ "@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": "^10.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.5.4" ++ "typescript": "~5.4.0" + }, + "resolutions": { +- "@types/react": "^17", +- "@types/react-dom": "^17" ++ "@types/react": "^18", ++ "@types/react-dom": "^18" + }, +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..839ff88 +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,27 @@ ++/* ++ * 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('/'); ++ ++ const enterButton = page.getByRole('button', { name: 'Enter' }); ++ await expect(enterButton).toBeVisible(); ++ await enterButton.click(); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 5509d17..346ffc1 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -8,26 +8,34 @@ + }, ++ "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.5.10", ++ "@backstage/catalog-model": "^1.6.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/core-app-api": "^1.14.2", ++ "@backstage/core-components": "^0.14.10", ++ "@backstage/core-plugin-api": "^1.9.3", ++ "@backstage/integration-react": "^1.1.30", ++ "@backstage/plugin-api-docs": "^0.11.8", ++ "@backstage/plugin-catalog": "^1.22.0", ++ "@backstage/plugin-catalog-common": "^1.0.26", ++ "@backstage/plugin-catalog-graph": "^0.4.8", ++ "@backstage/plugin-catalog-import": "^0.12.2", ++ "@backstage/plugin-catalog-react": "^1.12.3", ++ "@backstage/plugin-kubernetes": "^0.11.13", ++ "@backstage/plugin-org": "^0.6.28", ++ "@backstage/plugin-permission-react": "^0.4.25", ++ "@backstage/plugin-scaffolder": "^1.24.0", ++ "@backstage/plugin-search": "^1.4.15", ++ "@backstage/plugin-search-react": "^1.7.14", ++ "@backstage/plugin-techdocs": "^1.10.8", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.13", ++ "@backstage/plugin-techdocs-react": "^1.2.7", ++ "@backstage/plugin-user-settings": "^0.8.11", ++ "@backstage/theme": "^0.5.6", + "@material-ui/core": "^4.12.2", +@@ -35,31 +43,17 @@ + "history": "^5.0.0", +- "react": "^17.0.2", +- "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": "^18.0.2", ++ "react-dom": "^18.0.2", ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^1.0.1", +- "@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", ++ "@backstage/test-utils": "^1.5.10", ++ "@playwright/test": "^1.32.3", ++ "@testing-library/jest-dom": "^6.0.0", ++ "@testing-library/react": "^14.0.0", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^9.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..18da7c4 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -8,5 +8,4 @@ + name="description" +- content="Backstage is an open platform for building developer portals" ++ content="Backstage is an open source framework for building developer portals" + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index b94cac7..ec8ba1d 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { renderWithEffects } from '@backstage/test-utils'; ++import { render, waitFor } from '@testing-library/react'; + import App from './App'; +@@ -22,4 +22,7 @@ describe('App', () => { + +- const rendered = await renderWithEffects(); +- expect(rendered.baseElement).toBeInTheDocument(); ++ const rendered = render(); ++ ++ await waitFor(() => { ++ expect(rendered.baseElement).toBeInTheDocument(); ++ }); + }); +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index f4ff424..e24c468 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'; +@@ -15,3 +15,2 @@ import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +-import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +@@ -21,2 +20,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'; +@@ -27,7 +28,11 @@ import { Root } from './components/Root'; + +-import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; ++import { ++ AlertDisplay, ++ OAuthRequestDialog, ++ SignInPage, ++} 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 +45,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -46,2 +52,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -51,10 +58,10 @@ const app = createApp({ + }, ++ components: { ++ SignInPage: props => , ++ }, + }); + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -70,3 +77,7 @@ const routes = ( + element={} +- /> ++ > ++ ++ ++ ++ + } /> +@@ -74,9 +85,8 @@ const routes = ( + } +- /> +- } ++ element={ ++ ++ ++ ++ } + /> +@@ -90,4 +100,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -97,5 +107,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..853d175 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,22 +1,5 @@ +-/* +- * 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'; + import ExtensionIcon from '@material-ui/icons/Extension'; +-import MapIcon from '@material-ui/icons/MyLocation'; + import LibraryBooks from '@material-ui/icons/LibraryBooks'; +@@ -25,3 +8,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -34,3 +16,2 @@ import { + sidebarConfig, +- SidebarContext, + SidebarDivider, +@@ -41,2 +22,4 @@ import { + SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -44,2 +27,4 @@ import MenuIcon from '@material-ui/icons/Menu'; + import SearchIcon from '@material-ui/icons/Search'; ++import { MyGroupsSidebarItem } from '@backstage/plugin-org'; ++import GroupIcon from '@material-ui/icons/People'; + +@@ -62,3 +47,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -66,8 +51,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -89,2 +69,7 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + ++ + +@@ -95,3 +80,3 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ {/* Items in this group will be scrollable if they run out of space */} + +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..6164c76 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,7 +27,5 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +-import { +- isGithubActionsAvailable, +- EntityGithubActionsContent, +-} from '@backstage/plugin-github-actions'; + import { +@@ -70,2 +53,18 @@ import { + ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++import { ++ EntityKubernetesContent, ++ isKubernetesAvailable, ++} from '@backstage/plugin-kubernetes'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); ++ + const cicdContent = ( +@@ -74,5 +73,9 @@ const cicdContent = ( + +- +- +- ++ {/* ++ Here you can add support for different CI/CD services, for example ++ using @backstage-community/plugin-github-actions as follows: ++ ++ ++ ++ */} + +@@ -107,2 +110,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -146,2 +157,10 @@ const serviceEntityPage = ( + ++ ++ ++ ++ + +@@ -169,3 +188,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -184,2 +203,10 @@ const websiteEntityPage = ( + ++ ++ ++ ++ + +@@ -196,3 +223,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -215,3 +242,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -296,5 +323,8 @@ const groupPage = ( + +- ++ + + ++ ++ ++ + +@@ -315,3 +345,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/app/src/index.tsx b/packages/app/src/index.tsx +index b16aaf7..d875c77 100644 +--- a/packages/app/src/index.tsx ++++ b/packages/app/src/index.tsx +@@ -2,5 +2,5 @@ import '@backstage/cli/asset-types'; + import React from 'react'; +-import ReactDOM from 'react-dom'; ++import ReactDOM from 'react-dom/client'; + import App from './App'; + +-ReactDOM.render(, document.getElementById('root')); ++ReactDOM.createRoot(document.getElementById('root')!).render(); +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index dd35d4d..bef67b5 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,15 @@ 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 --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ ++ yarn install --frozen-lockfile --production --network-timeout 300000 + +-RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" ++# This will include the examples, if you don't need these simply remove this line ++COPY --chown=node:node examples ./examples + + # 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..6a14abc 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -18,23 +18,31 @@ + "dependencies": { ++ "@backstage/backend-common": "^0.24.0", ++ "@backstage/backend-defaults": "^0.4.2", ++ "@backstage/backend-tasks": "^0.6.0", ++ "@backstage/config": "^1.2.0", ++ "@backstage/plugin-app-backend": "^0.3.72", ++ "@backstage/plugin-auth-backend": "^0.22.10", ++ "@backstage/plugin-auth-backend-module-github-provider": "^0.1.20", ++ "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.9", ++ "@backstage/plugin-auth-node": "^0.5.0", ++ "@backstage/plugin-catalog-backend": "^1.25.0", ++ "@backstage/plugin-catalog-backend-module-logs": "^0.0.2", ++ "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.21", ++ "@backstage/plugin-kubernetes-backend": "^0.18.4", ++ "@backstage/plugin-permission-backend": "^0.5.47", ++ "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.1.20", ++ "@backstage/plugin-permission-common": "^0.8.1", ++ "@backstage/plugin-permission-node": "^0.8.1", ++ "@backstage/plugin-proxy-backend": "^0.5.4", ++ "@backstage/plugin-scaffolder-backend": "^1.24.0", ++ "@backstage/plugin-search-backend": "^1.5.15", ++ "@backstage/plugin-search-backend-module-catalog": "^0.2.0", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.33", ++ "@backstage/plugin-search-backend-module-techdocs": "^0.2.0", ++ "@backstage/plugin-search-backend-node": "^1.3.0", ++ "@backstage/plugin-techdocs-backend": "^1.10.10", + "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", +- "dockerode": "^3.3.1", +- "express": "^4.17.1", +- "express-promise-router": "^4.1.0", +- "luxon": "^2.0.2", +- "better-sqlite3": "^7.5.0", ++ "better-sqlite3": "^9.0.0", ++ "node-gyp": "^10.0.0", ++ "pg": "^8.11.3", + "winston": "^3.2.1" +@@ -42,4 +50,3 @@ + "devDependencies": { +- "@backstage/cli": "^0.17.0", +- "@types/dockerode": "^3.3.0", ++ "@backstage/cli": "^0.27.0", + "@types/express": "^4.17.6", +diff --git a/packages/backend/src/index.test.ts b/packages/backend/src/index.test.ts +deleted file mode 100644 +index 7814b8c..0000000 +--- a/packages/backend/src/index.test.ts ++++ /dev/null +@@ -1,8 +0,0 @@ +-import { PluginEnvironment } from './types'; +- +-describe('test', () => { +- it('unbreaks the test runner', () => { +- const unbreaker = {} as PluginEnvironment; +- expect(unbreaker).toBeTruthy(); +- }); +-}); +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 70bc66b..f57d4c5 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -8,102 +8,47 @@ + +-import Router from 'express-promise-router'; +-import { +- createServiceBuilder, +- loadBackendConfig, +- getRootLogger, +- useHotMemoize, +- notFoundHandler, +- CacheManager, +- DatabaseManager, +- SingleHostDiscovery, +- UrlReaders, +- ServerTokenManager, +-} from '@backstage/backend-common'; +-import { TaskScheduler } from '@backstage/backend-tasks'; +-import { Config } from '@backstage/config'; +-import app from './plugins/app'; +-import auth from './plugins/auth'; +-import catalog from './plugins/catalog'; +-import scaffolder from './plugins/scaffolder'; +-import proxy from './plugins/proxy'; +-import techdocs from './plugins/techdocs'; +-import search from './plugins/search'; +-import { PluginEnvironment } from './types'; +-import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { createBackend } from '@backstage/backend-defaults'; + +-function makeCreateEnv(config: Config) { +- const root = getRootLogger(); +- const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); +- const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); +- const permissions = ServerPermissionClient.fromConfig(config, { +- discovery, +- tokenManager, +- }); ++const backend = createBackend(); + +- root.info(`Created UrlReader ${reader}`); ++backend.add(import('@backstage/plugin-app-backend/alpha')); ++backend.add(import('@backstage/plugin-proxy-backend/alpha')); ++backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); ++backend.add(import('@backstage/plugin-techdocs-backend/alpha')); + +- return (plugin: string): PluginEnvironment => { +- const logger = root.child({ type: 'plugin', plugin }); +- const database = databaseManager.forPlugin(plugin); +- const cache = cacheManager.forPlugin(plugin); +- const scheduler = taskScheduler.forPlugin(plugin); +- return { +- logger, +- database, +- cache, +- config, +- reader, +- discovery, +- tokenManager, +- scheduler, +- permissions, +- }; +- }; +-} ++// auth plugin ++backend.add(import('@backstage/plugin-auth-backend')); ++// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin ++backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); ++// See https://backstage.io/docs/auth/guest/provider + +-async function main() { +- const config = await loadBackendConfig({ +- argv: process.argv, +- logger: getRootLogger(), +- }); +- const createEnv = makeCreateEnv(config); ++// catalog plugin ++backend.add(import('@backstage/plugin-catalog-backend/alpha')); ++backend.add( ++ import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ++); + +- const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); +- const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); +- const authEnv = useHotMemoize(module, () => createEnv('auth')); +- const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); +- const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); +- const searchEnv = useHotMemoize(module, () => createEnv('search')); +- const appEnv = useHotMemoize(module, () => createEnv('app')); ++// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors ++backend.add(import('@backstage/plugin-catalog-backend-module-logs')); + +- const apiRouter = Router(); +- apiRouter.use('/catalog', await catalog(catalogEnv)); +- apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); +- apiRouter.use('/auth', await auth(authEnv)); +- apiRouter.use('/techdocs', await techdocs(techdocsEnv)); +- apiRouter.use('/proxy', await proxy(proxyEnv)); +- apiRouter.use('/search', await search(searchEnv)); ++// permission plugin ++backend.add(import('@backstage/plugin-permission-backend/alpha')); ++// See https://backstage.io/docs/permissions/getting-started for how to create your own permission policy ++backend.add( ++ import('@backstage/plugin-permission-backend-module-allow-all-policy'), ++); + +- // Add backends ABOVE this line; this 404 handler is the catch-all fallback +- apiRouter.use(notFoundHandler()); ++// search plugin ++backend.add(import('@backstage/plugin-search-backend/alpha')); + +- const service = createServiceBuilder(module) +- .loadConfig(config) +- .addRouter('/api', apiRouter) +- .addRouter('', await app(appEnv)); ++// search engine ++// See https://backstage.io/docs/features/search/search-engines ++backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); + +- await service.start().catch(err => { +- console.log(err); +- process.exit(1); +- }); +-} ++// search collators ++backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); ++backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); + +-module.hot?.accept(); +-main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); +- process.exit(1); +-}); ++// kubernetes ++backend.add(import('@backstage/plugin-kubernetes-backend/alpha')); ++ ++backend.start(); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +deleted file mode 100644 +index 7c37f68..0000000 +--- a/packages/backend/src/plugins/app.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { createRouter } from '@backstage/plugin-app-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- return await createRouter({ +- 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 +deleted file mode 100644 +index 1476e66..0000000 +--- a/packages/backend/src/plugins/auth.ts ++++ /dev/null +@@ -1,15 +0,0 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-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, +- }); +-} +diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts +deleted file mode 100644 +index 876cb6b..0000000 +--- a/packages/backend/src/plugins/catalog.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +-import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const builder = await CatalogBuilder.create(env); +- builder.addProcessor(new ScaffolderEntitiesProcessor()); +- const { processingEngine, router } = await builder.build(); +- await processingEngine.start(); +- return router; +-} +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +deleted file mode 100644 +index 54ec393..0000000 +--- a/packages/backend/src/plugins/proxy.ts ++++ /dev/null +@@ -1,13 +0,0 @@ +-import { createRouter } from '@backstage/plugin-proxy-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-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 +deleted file mode 100644 +index 7ce5fcf..0000000 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ /dev/null +@@ -1,20 +0,0 @@ +-import { CatalogClient } from '@backstage/catalog-client'; +-import { createRouter } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import type { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const catalogClient = new CatalogClient({ +- discoveryApi: env.discovery, +- }); +- +- return await createRouter({ +- logger: env.logger, +- config: env.config, +- database: env.database, +- reader: env.reader, +- catalogClient, +- }); +-} +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +deleted file mode 100644 +index 27c42fc..0000000 +--- a/packages/backend/src/plugins/search.ts ++++ /dev/null +@@ -1,67 +0,0 @@ +-import { useHotCleanup } from '@backstage/backend-common'; +-import { createRouter } from '@backstage/plugin-search-backend'; +-import { +- IndexBuilder, +- LunrSearchEngine, +-} from '@backstage/plugin-search-backend-node'; +-import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; +-import { Router } from 'express'; +-import { Duration } from 'luxon'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ +- logger: env.logger, +- }); +- const indexBuilder = new IndexBuilder({ +- logger: env.logger, +- searchEngine, +- }); +- +- const schedule = env.scheduler.createScheduledTaskRunner({ +- frequency: Duration.fromObject({ minutes: 10 }), +- timeout: Duration.fromObject({ 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 }), +- }); +- +- // Collators are responsible for gathering documents known to plugins. This +- // 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({ +- schedule, +- factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { +- discovery: env.discovery, +- logger: env.logger, +- tokenManager: env.tokenManager, +- }), +- }); +- +- // The scheduler controls when documents are gathered from collators and sent +- // to the search engine for indexing. +- const { scheduler } = await indexBuilder.build(); +- scheduler.start(); +- +- useHotCleanup(module, () => scheduler.stop()); +- +- return await createRouter({ +- engine: indexBuilder.getSearchEngine(), +- 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 +deleted file mode 100644 +index be8bb0c..0000000 +--- a/packages/backend/src/plugins/techdocs.ts ++++ /dev/null +@@ -1,51 +0,0 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; +-import { +- createRouter, +- Generators, +- Preparers, +- Publisher, +-} from '@backstage/plugin-techdocs-backend'; +-import Docker from 'dockerode'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(env.config, { +- logger: env.logger, +- reader: env.reader, +- }); +- +- // Docker client (conditionally) used by the generators, based on techdocs.generators config. +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- +- // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(env.config, { +- logger: env.logger, +- containerRunner, +- }); +- +- // Publisher is used for +- // 1. Publishing generated files to storage +- // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(env.config, { +- logger: env.logger, +- discovery: env.discovery, +- }); +- +- // checks if the publisher is working and logs the result +- await publisher.getReadiness(); +- +- return await createRouter({ +- preparers, +- generators, +- publisher, +- 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 +deleted file mode 100644 +index 8e0a864..0000000 +--- a/packages/backend/src/types.ts ++++ /dev/null +@@ -1,23 +0,0 @@ +-import { Logger } from 'winston'; +-import { Config } from '@backstage/config'; +-import { +- PluginCacheManager, +- PluginDatabaseManager, +- PluginEndpointDiscovery, +- TokenManager, +- UrlReader, +-} from '@backstage/backend-common'; +-import { PluginTaskScheduler } from '@backstage/backend-tasks'; +-import { PermissionEvaluator } from '@backstage/plugin-permission-common'; +- +-export type PluginEnvironment = { +- logger: Logger; +- database: PluginDatabaseManager; +- cache: PluginCacheManager; +- config: Config; +- reader: UrlReader; +- discovery: PluginEndpointDiscovery; +- tokenManager: TokenManager; +- scheduler: PluginTaskScheduler; +- permissions: PermissionEvaluator; +-}; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..733be13 +--- /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 dev', ++ 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..c37fc61 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1,3 @@ ++# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. ++# yarn lockfile v1 ++ diff --git a/diffs/0.4.27..0.5.18.diff b/diffs/0.4.27..0.5.18.diff new file mode 100644 index 00000000..caf991f3 --- /dev/null +++ b/diffs/0.4.27..0.5.18.diff @@ -0,0 +1,1615 @@ +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..5d426f5 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -11,12 +11,7 @@ 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' + +- # config options: https://node-postgres.com/api/client ++ # config options: https://node-postgres.com/apis/client + database: +@@ -34 +29,27 @@ backend: + # $file: /ca/server.crt ++ ++auth: ++ providers: ++ guest: {} ++ ++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: ++ # Local example data, replace this with your production config, these are intended for demo use only. ++ # File locations are relative to the backend process, typically in a deployed context, such as in a Docker container, this will be the root ++ - type: file ++ target: ./examples/entities.yaml ++ ++ # Local example template ++ - type: file ++ target: ./examples/template/template.yaml ++ rules: ++ - allow: [Template] ++ ++ # Local example organizational data ++ - type: file ++ target: ./examples/org.yaml ++ rules: ++ - allow: [User, Group] +diff --git a/app-config.yaml b/app-config.yaml +index cd6998d..ca52ec5 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 +@@ -41,3 +38,3 @@ integrations: + # 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 ++ # about setting up the GitHub integration here: https://backstage.io/docs/integrations/github/locations#configuration + token: ${GITHUB_TOKEN} +@@ -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 + +@@ -66,3 +66,5 @@ auth: + # see https://backstage.io/docs/auth/ to learn about auth providers +- providers: {} ++ providers: ++ # See https://backstage.io/docs/auth/guest/provider ++ guest: {} + +@@ -103 +105,9 @@ catalog: + # - allow: [User, Group] ++ ++kubernetes: ++ # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options ++ ++# see https://backstage.io/docs/permissions/getting-started for more on the permission framework ++permission: ++ # setting this to `false` will disable permissions ++ enabled: true +diff --git a/backstage.json b/backstage.json +index a15e91f..230d39d 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "1.2.0" ++ "version": "1.30.0" + } +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..80a963e 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,12 +33,15 @@ + "devDependencies": { +- "@backstage/cli": "^0.17.1", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/e2e-test-utils": "^0.1.1", ++ "@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": "^10.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.6.4" ++ "typescript": "~5.4.0" + }, + "resolutions": { +- "@types/react": "^17", +- "@types/react-dom": "^17" ++ "@types/react": "^18", ++ "@types/react-dom": "^18" + }, +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..839ff88 +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,27 @@ ++/* ++ * 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('/'); ++ ++ const enterButton = page.getByRole('button', { name: 'Enter' }); ++ await expect(enterButton).toBeVisible(); ++ await enterButton.click(); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 7f04bae..346ffc1 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -8,28 +8,34 @@ + }, ++ "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.5.10", ++ "@backstage/catalog-model": "^1.6.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/core-app-api": "^1.14.2", ++ "@backstage/core-components": "^0.14.10", ++ "@backstage/core-plugin-api": "^1.9.3", ++ "@backstage/integration-react": "^1.1.30", ++ "@backstage/plugin-api-docs": "^0.11.8", ++ "@backstage/plugin-catalog": "^1.22.0", ++ "@backstage/plugin-catalog-common": "^1.0.26", ++ "@backstage/plugin-catalog-graph": "^0.4.8", ++ "@backstage/plugin-catalog-import": "^0.12.2", ++ "@backstage/plugin-catalog-react": "^1.12.3", ++ "@backstage/plugin-kubernetes": "^0.11.13", ++ "@backstage/plugin-org": "^0.6.28", ++ "@backstage/plugin-permission-react": "^0.4.25", ++ "@backstage/plugin-scaffolder": "^1.24.0", ++ "@backstage/plugin-search": "^1.4.15", ++ "@backstage/plugin-search-react": "^1.7.14", ++ "@backstage/plugin-techdocs": "^1.10.8", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.13", ++ "@backstage/plugin-techdocs-react": "^1.2.7", ++ "@backstage/plugin-user-settings": "^0.8.11", ++ "@backstage/theme": "^0.5.6", + "@material-ui/core": "^4.12.2", +@@ -37,31 +43,17 @@ + "history": "^5.0.0", +- "react": "^17.0.2", +- "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": "^18.0.2", ++ "react-dom": "^18.0.2", ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^1.1.0", +- "@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", ++ "@backstage/test-utils": "^1.5.10", ++ "@playwright/test": "^1.32.3", ++ "@testing-library/jest-dom": "^6.0.0", ++ "@testing-library/react": "^14.0.0", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^9.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..18da7c4 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -8,5 +8,4 @@ + name="description" +- content="Backstage is an open platform for building developer portals" ++ content="Backstage is an open source framework for building developer portals" + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index b94cac7..ec8ba1d 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { renderWithEffects } from '@backstage/test-utils'; ++import { render, waitFor } from '@testing-library/react'; + import App from './App'; +@@ -22,4 +22,7 @@ describe('App', () => { + +- const rendered = await renderWithEffects(); +- expect(rendered.baseElement).toBeInTheDocument(); ++ const rendered = render(); ++ ++ await waitFor(() => { ++ expect(rendered.baseElement).toBeInTheDocument(); ++ }); + }); +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index c487726..e24c468 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'; +@@ -15,3 +15,2 @@ import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +-import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +@@ -29,7 +28,11 @@ import { Root } from './components/Root'; + +-import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; ++import { ++ AlertDisplay, ++ OAuthRequestDialog, ++ SignInPage, ++} 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 +45,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -48,2 +52,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -53,10 +58,10 @@ const app = createApp({ + }, ++ components: { ++ SignInPage: props => , ++ }, + }); + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -80,9 +85,8 @@ const routes = ( + } +- /> +- } ++ element={ ++ ++ ++ ++ } + /> +@@ -96,4 +100,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -103,5 +107,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..853d175 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,22 +1,5 @@ +-/* +- * 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'; + import ExtensionIcon from '@material-ui/icons/Extension'; +-import MapIcon from '@material-ui/icons/MyLocation'; + import LibraryBooks from '@material-ui/icons/LibraryBooks'; +@@ -25,3 +8,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -34,3 +16,2 @@ import { + sidebarConfig, +- SidebarContext, + SidebarDivider, +@@ -41,2 +22,4 @@ import { + SidebarSpace, ++ useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -44,2 +27,4 @@ import MenuIcon from '@material-ui/icons/Menu'; + import SearchIcon from '@material-ui/icons/Search'; ++import { MyGroupsSidebarItem } from '@backstage/plugin-org'; ++import GroupIcon from '@material-ui/icons/People'; + +@@ -62,3 +47,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -66,8 +51,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -89,2 +69,7 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + ++ + +@@ -95,3 +80,3 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ {/* Items in this group will be scrollable if they run out of space */} + +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..6164c76 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,7 +27,5 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +-import { +- isGithubActionsAvailable, +- EntityGithubActionsContent, +-} from '@backstage/plugin-github-actions'; + import { +@@ -70,2 +53,18 @@ import { + ++import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; ++ ++import { ++ EntityKubernetesContent, ++ isKubernetesAvailable, ++} from '@backstage/plugin-kubernetes'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); ++ + const cicdContent = ( +@@ -74,5 +73,9 @@ const cicdContent = ( + +- +- +- ++ {/* ++ Here you can add support for different CI/CD services, for example ++ using @backstage-community/plugin-github-actions as follows: ++ ++ ++ ++ */} + +@@ -107,2 +110,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -146,2 +157,10 @@ const serviceEntityPage = ( + ++ ++ ++ ++ + +@@ -169,3 +188,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -184,2 +203,10 @@ const websiteEntityPage = ( + ++ ++ ++ ++ + +@@ -196,3 +223,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -215,3 +242,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -296,5 +323,8 @@ const groupPage = ( + +- ++ + + ++ ++ ++ + +@@ -315,3 +345,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/app/src/index.tsx b/packages/app/src/index.tsx +index b16aaf7..d875c77 100644 +--- a/packages/app/src/index.tsx ++++ b/packages/app/src/index.tsx +@@ -2,5 +2,5 @@ import '@backstage/cli/asset-types'; + import React from 'react'; +-import ReactDOM from 'react-dom'; ++import ReactDOM from 'react-dom/client'; + import App from './App'; + +-ReactDOM.render(, document.getElementById('root')); ++ReactDOM.createRoot(document.getElementById('root')!).render(); +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index a5773aa..bef67b5 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,13 @@ 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 ++ ++# This will include the examples, if you don't need these simply remove this line ++COPY --chown=node:node examples ./examples + + # 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..6a14abc 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -18,23 +18,31 @@ + "dependencies": { ++ "@backstage/backend-common": "^0.24.0", ++ "@backstage/backend-defaults": "^0.4.2", ++ "@backstage/backend-tasks": "^0.6.0", ++ "@backstage/config": "^1.2.0", ++ "@backstage/plugin-app-backend": "^0.3.72", ++ "@backstage/plugin-auth-backend": "^0.22.10", ++ "@backstage/plugin-auth-backend-module-github-provider": "^0.1.20", ++ "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.9", ++ "@backstage/plugin-auth-node": "^0.5.0", ++ "@backstage/plugin-catalog-backend": "^1.25.0", ++ "@backstage/plugin-catalog-backend-module-logs": "^0.0.2", ++ "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.21", ++ "@backstage/plugin-kubernetes-backend": "^0.18.4", ++ "@backstage/plugin-permission-backend": "^0.5.47", ++ "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.1.20", ++ "@backstage/plugin-permission-common": "^0.8.1", ++ "@backstage/plugin-permission-node": "^0.8.1", ++ "@backstage/plugin-proxy-backend": "^0.5.4", ++ "@backstage/plugin-scaffolder-backend": "^1.24.0", ++ "@backstage/plugin-search-backend": "^1.5.15", ++ "@backstage/plugin-search-backend-module-catalog": "^0.2.0", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.33", ++ "@backstage/plugin-search-backend-module-techdocs": "^0.2.0", ++ "@backstage/plugin-search-backend-node": "^1.3.0", ++ "@backstage/plugin-techdocs-backend": "^1.10.10", + "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", +- "dockerode": "^3.3.1", +- "express": "^4.17.1", +- "express-promise-router": "^4.1.0", +- "pg": "^8.3.0", ++ "better-sqlite3": "^9.0.0", ++ "node-gyp": "^10.0.0", ++ "pg": "^8.11.3", + "winston": "^3.2.1" +@@ -42,8 +50,6 @@ + "devDependencies": { +- "@backstage/cli": "^0.17.1", +- "@types/dockerode": "^3.3.0", +- "@types/express-serve-static-core": "^4.17.5", ++ "@backstage/cli": "^0.27.0", + "@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.test.ts b/packages/backend/src/index.test.ts +deleted file mode 100644 +index 7814b8c..0000000 +--- a/packages/backend/src/index.test.ts ++++ /dev/null +@@ -1,8 +0,0 @@ +-import { PluginEnvironment } from './types'; +- +-describe('test', () => { +- it('unbreaks the test runner', () => { +- const unbreaker = {} as PluginEnvironment; +- expect(unbreaker).toBeTruthy(); +- }); +-}); +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 70bc66b..f57d4c5 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -8,102 +8,47 @@ + +-import Router from 'express-promise-router'; +-import { +- createServiceBuilder, +- loadBackendConfig, +- getRootLogger, +- useHotMemoize, +- notFoundHandler, +- CacheManager, +- DatabaseManager, +- SingleHostDiscovery, +- UrlReaders, +- ServerTokenManager, +-} from '@backstage/backend-common'; +-import { TaskScheduler } from '@backstage/backend-tasks'; +-import { Config } from '@backstage/config'; +-import app from './plugins/app'; +-import auth from './plugins/auth'; +-import catalog from './plugins/catalog'; +-import scaffolder from './plugins/scaffolder'; +-import proxy from './plugins/proxy'; +-import techdocs from './plugins/techdocs'; +-import search from './plugins/search'; +-import { PluginEnvironment } from './types'; +-import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { createBackend } from '@backstage/backend-defaults'; + +-function makeCreateEnv(config: Config) { +- const root = getRootLogger(); +- const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); +- const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); +- const permissions = ServerPermissionClient.fromConfig(config, { +- discovery, +- tokenManager, +- }); ++const backend = createBackend(); + +- root.info(`Created UrlReader ${reader}`); ++backend.add(import('@backstage/plugin-app-backend/alpha')); ++backend.add(import('@backstage/plugin-proxy-backend/alpha')); ++backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); ++backend.add(import('@backstage/plugin-techdocs-backend/alpha')); + +- return (plugin: string): PluginEnvironment => { +- const logger = root.child({ type: 'plugin', plugin }); +- const database = databaseManager.forPlugin(plugin); +- const cache = cacheManager.forPlugin(plugin); +- const scheduler = taskScheduler.forPlugin(plugin); +- return { +- logger, +- database, +- cache, +- config, +- reader, +- discovery, +- tokenManager, +- scheduler, +- permissions, +- }; +- }; +-} ++// auth plugin ++backend.add(import('@backstage/plugin-auth-backend')); ++// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin ++backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); ++// See https://backstage.io/docs/auth/guest/provider + +-async function main() { +- const config = await loadBackendConfig({ +- argv: process.argv, +- logger: getRootLogger(), +- }); +- const createEnv = makeCreateEnv(config); ++// catalog plugin ++backend.add(import('@backstage/plugin-catalog-backend/alpha')); ++backend.add( ++ import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ++); + +- const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); +- const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); +- const authEnv = useHotMemoize(module, () => createEnv('auth')); +- const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); +- const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); +- const searchEnv = useHotMemoize(module, () => createEnv('search')); +- const appEnv = useHotMemoize(module, () => createEnv('app')); ++// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors ++backend.add(import('@backstage/plugin-catalog-backend-module-logs')); + +- const apiRouter = Router(); +- apiRouter.use('/catalog', await catalog(catalogEnv)); +- apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); +- apiRouter.use('/auth', await auth(authEnv)); +- apiRouter.use('/techdocs', await techdocs(techdocsEnv)); +- apiRouter.use('/proxy', await proxy(proxyEnv)); +- apiRouter.use('/search', await search(searchEnv)); ++// permission plugin ++backend.add(import('@backstage/plugin-permission-backend/alpha')); ++// See https://backstage.io/docs/permissions/getting-started for how to create your own permission policy ++backend.add( ++ import('@backstage/plugin-permission-backend-module-allow-all-policy'), ++); + +- // Add backends ABOVE this line; this 404 handler is the catch-all fallback +- apiRouter.use(notFoundHandler()); ++// search plugin ++backend.add(import('@backstage/plugin-search-backend/alpha')); + +- const service = createServiceBuilder(module) +- .loadConfig(config) +- .addRouter('/api', apiRouter) +- .addRouter('', await app(appEnv)); ++// search engine ++// See https://backstage.io/docs/features/search/search-engines ++backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); + +- await service.start().catch(err => { +- console.log(err); +- process.exit(1); +- }); +-} ++// search collators ++backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); ++backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); + +-module.hot?.accept(); +-main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); +- process.exit(1); +-}); ++// kubernetes ++backend.add(import('@backstage/plugin-kubernetes-backend/alpha')); ++ ++backend.start(); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +deleted file mode 100644 +index 7c37f68..0000000 +--- a/packages/backend/src/plugins/app.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { createRouter } from '@backstage/plugin-app-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- return await createRouter({ +- 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 +deleted file mode 100644 +index 13f18c5..0000000 +--- a/packages/backend/src/plugins/auth.ts ++++ /dev/null +@@ -1,36 +0,0 @@ +-import { +- createRouter, +- providers, +- defaultAuthProviderFactories, +-} from '@backstage/plugin-auth-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-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 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: +- // +- // https://backstage.io/docs/auth/identity-resolver +- github: providers.github.create({ +- // signIn: { +- // resolver: providers.github.resolvers.usernameMatchingUserEntityName(), +- // }, +- }), +- }, +- }); +-} +diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts +deleted file mode 100644 +index 876cb6b..0000000 +--- a/packages/backend/src/plugins/catalog.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +-import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const builder = await CatalogBuilder.create(env); +- builder.addProcessor(new ScaffolderEntitiesProcessor()); +- const { processingEngine, router } = await builder.build(); +- await processingEngine.start(); +- return router; +-} +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +deleted file mode 100644 +index 54ec393..0000000 +--- a/packages/backend/src/plugins/proxy.ts ++++ /dev/null +@@ -1,13 +0,0 @@ +-import { createRouter } from '@backstage/plugin-proxy-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-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 +deleted file mode 100644 +index 7ce5fcf..0000000 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ /dev/null +@@ -1,20 +0,0 @@ +-import { CatalogClient } from '@backstage/catalog-client'; +-import { createRouter } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import type { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const catalogClient = new CatalogClient({ +- discoveryApi: env.discovery, +- }); +- +- return await createRouter({ +- logger: env.logger, +- config: env.config, +- database: env.database, +- reader: env.reader, +- catalogClient, +- }); +-} +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +deleted file mode 100644 +index e9469dc..0000000 +--- a/packages/backend/src/plugins/search.ts ++++ /dev/null +@@ -1,66 +0,0 @@ +-import { useHotCleanup } from '@backstage/backend-common'; +-import { createRouter } from '@backstage/plugin-search-backend'; +-import { +- IndexBuilder, +- LunrSearchEngine, +-} from '@backstage/plugin-search-backend-node'; +-import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; +-import { Router } from 'express'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- // Initialize a connection to a search engine. +- 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 +- // 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({ +- schedule, +- factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { +- discovery: env.discovery, +- logger: env.logger, +- tokenManager: env.tokenManager, +- }), +- }); +- +- // The scheduler controls when documents are gathered from collators and sent +- // to the search engine for indexing. +- const { scheduler } = await indexBuilder.build(); +- scheduler.start(); +- +- useHotCleanup(module, () => scheduler.stop()); +- +- return await createRouter({ +- engine: indexBuilder.getSearchEngine(), +- 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 +deleted file mode 100644 +index be8bb0c..0000000 +--- a/packages/backend/src/plugins/techdocs.ts ++++ /dev/null +@@ -1,51 +0,0 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; +-import { +- createRouter, +- Generators, +- Preparers, +- Publisher, +-} from '@backstage/plugin-techdocs-backend'; +-import Docker from 'dockerode'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(env.config, { +- logger: env.logger, +- reader: env.reader, +- }); +- +- // Docker client (conditionally) used by the generators, based on techdocs.generators config. +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- +- // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(env.config, { +- logger: env.logger, +- containerRunner, +- }); +- +- // Publisher is used for +- // 1. Publishing generated files to storage +- // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(env.config, { +- logger: env.logger, +- discovery: env.discovery, +- }); +- +- // checks if the publisher is working and logs the result +- await publisher.getReadiness(); +- +- return await createRouter({ +- preparers, +- generators, +- publisher, +- 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 +deleted file mode 100644 +index 8e0a864..0000000 +--- a/packages/backend/src/types.ts ++++ /dev/null +@@ -1,23 +0,0 @@ +-import { Logger } from 'winston'; +-import { Config } from '@backstage/config'; +-import { +- PluginCacheManager, +- PluginDatabaseManager, +- PluginEndpointDiscovery, +- TokenManager, +- UrlReader, +-} from '@backstage/backend-common'; +-import { PluginTaskScheduler } from '@backstage/backend-tasks'; +-import { PermissionEvaluator } from '@backstage/plugin-permission-common'; +- +-export type PluginEnvironment = { +- logger: Logger; +- database: PluginDatabaseManager; +- cache: PluginCacheManager; +- config: Config; +- reader: UrlReader; +- discovery: PluginEndpointDiscovery; +- tokenManager: TokenManager; +- scheduler: PluginTaskScheduler; +- permissions: PermissionEvaluator; +-}; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..733be13 +--- /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 dev', ++ 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..c37fc61 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1,3 @@ ++# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. ++# yarn lockfile v1 ++ diff --git a/diffs/0.4.28..0.5.18.diff b/diffs/0.4.28..0.5.18.diff new file mode 100644 index 00000000..42feea7f --- /dev/null +++ b/diffs/0.4.28..0.5.18.diff @@ -0,0 +1,1596 @@ +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..5d426f5 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -11,12 +11,7 @@ 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' + +- # config options: https://node-postgres.com/api/client ++ # config options: https://node-postgres.com/apis/client + database: +@@ -35,6 +30,26 @@ backend: + ++auth: ++ providers: ++ guest: {} ++ + 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. +- locations: [] ++ locations: ++ # Local example data, replace this with your production config, these are intended for demo use only. ++ # File locations are relative to the backend process, typically in a deployed context, such as in a Docker container, this will be the root ++ - type: file ++ target: ./examples/entities.yaml ++ ++ # Local example template ++ - type: file ++ target: ./examples/template/template.yaml ++ rules: ++ - allow: [Template] ++ ++ # Local example organizational data ++ - type: file ++ target: ./examples/org.yaml ++ rules: ++ - allow: [User, Group] +diff --git a/app-config.yaml b/app-config.yaml +index cd6998d..ca52ec5 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 +@@ -41,3 +38,3 @@ integrations: + # 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 ++ # about setting up the GitHub integration here: https://backstage.io/docs/integrations/github/locations#configuration + token: ${GITHUB_TOKEN} +@@ -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 + +@@ -66,3 +66,5 @@ auth: + # see https://backstage.io/docs/auth/ to learn about auth providers +- providers: {} ++ providers: ++ # See https://backstage.io/docs/auth/guest/provider ++ guest: {} + +@@ -103 +105,9 @@ catalog: + # - allow: [User, Group] ++ ++kubernetes: ++ # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options ++ ++# see https://backstage.io/docs/permissions/getting-started for more on the permission framework ++permission: ++ # setting this to `false` will disable permissions ++ enabled: true +diff --git a/backstage.json b/backstage.json +index b23367d..230d39d 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "1.3.0" ++ "version": "1.30.0" + } +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..80a963e 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,12 +33,15 @@ + "devDependencies": { +- "@backstage/cli": "^0.17.2", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/e2e-test-utils": "^0.1.1", ++ "@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": "^10.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.6.4" ++ "typescript": "~5.4.0" + }, + "resolutions": { +- "@types/react": "^17", +- "@types/react-dom": "^17" ++ "@types/react": "^18", ++ "@types/react-dom": "^18" + }, +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..839ff88 +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,27 @@ ++/* ++ * 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('/'); ++ ++ const enterButton = page.getByRole('button', { name: 'Enter' }); ++ await expect(enterButton).toBeVisible(); ++ await enterButton.click(); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index f4cbcda..346ffc1 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -8,28 +8,34 @@ + }, ++ "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.5.10", ++ "@backstage/catalog-model": "^1.6.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/core-app-api": "^1.14.2", ++ "@backstage/core-components": "^0.14.10", ++ "@backstage/core-plugin-api": "^1.9.3", ++ "@backstage/integration-react": "^1.1.30", ++ "@backstage/plugin-api-docs": "^0.11.8", ++ "@backstage/plugin-catalog": "^1.22.0", ++ "@backstage/plugin-catalog-common": "^1.0.26", ++ "@backstage/plugin-catalog-graph": "^0.4.8", ++ "@backstage/plugin-catalog-import": "^0.12.2", ++ "@backstage/plugin-catalog-react": "^1.12.3", ++ "@backstage/plugin-kubernetes": "^0.11.13", ++ "@backstage/plugin-org": "^0.6.28", ++ "@backstage/plugin-permission-react": "^0.4.25", ++ "@backstage/plugin-scaffolder": "^1.24.0", ++ "@backstage/plugin-search": "^1.4.15", ++ "@backstage/plugin-search-react": "^1.7.14", ++ "@backstage/plugin-techdocs": "^1.10.8", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.13", ++ "@backstage/plugin-techdocs-react": "^1.2.7", ++ "@backstage/plugin-user-settings": "^0.8.11", ++ "@backstage/theme": "^0.5.6", + "@material-ui/core": "^4.12.2", +@@ -37,31 +43,17 @@ + "history": "^5.0.0", +- "react": "^17.0.2", +- "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": "^18.0.2", ++ "react-dom": "^18.0.2", ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^1.1.1", +- "@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", ++ "@backstage/test-utils": "^1.5.10", ++ "@playwright/test": "^1.32.3", ++ "@testing-library/jest-dom": "^6.0.0", ++ "@testing-library/react": "^14.0.0", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^9.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..18da7c4 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -8,5 +8,4 @@ + name="description" +- content="Backstage is an open platform for building developer portals" ++ content="Backstage is an open source framework for building developer portals" + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index b94cac7..ec8ba1d 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { renderWithEffects } from '@backstage/test-utils'; ++import { render, waitFor } from '@testing-library/react'; + import App from './App'; +@@ -22,4 +22,7 @@ describe('App', () => { + +- const rendered = await renderWithEffects(); +- expect(rendered.baseElement).toBeInTheDocument(); ++ const rendered = render(); ++ ++ await waitFor(() => { ++ expect(rendered.baseElement).toBeInTheDocument(); ++ }); + }); +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index c487726..e24c468 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'; +@@ -15,3 +15,2 @@ import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +-import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +@@ -29,7 +28,11 @@ import { Root } from './components/Root'; + +-import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; ++import { ++ AlertDisplay, ++ OAuthRequestDialog, ++ SignInPage, ++} 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 +45,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -48,2 +52,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -53,10 +58,10 @@ const app = createApp({ + }, ++ components: { ++ SignInPage: props => , ++ }, + }); + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -80,9 +85,8 @@ const routes = ( + } +- /> +- } ++ element={ ++ ++ ++ ++ } + /> +@@ -96,4 +100,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -103,5 +107,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..853d175 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,22 +1,5 @@ +-/* +- * 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'; + import ExtensionIcon from '@material-ui/icons/Extension'; +-import MapIcon from '@material-ui/icons/MyLocation'; + import LibraryBooks from '@material-ui/icons/LibraryBooks'; +@@ -25,3 +8,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -41,2 +23,3 @@ import { + useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -44,2 +27,4 @@ import MenuIcon from '@material-ui/icons/Menu'; + import SearchIcon from '@material-ui/icons/Search'; ++import { MyGroupsSidebarItem } from '@backstage/plugin-org'; ++import GroupIcon from '@material-ui/icons/People'; + +@@ -66,9 +51,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -90,2 +69,7 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + ++ + +@@ -96,3 +80,3 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ {/* Items in this group will be scrollable if they run out of space */} + +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..6164c76 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,7 +27,5 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +-import { +- isGithubActionsAvailable, +- EntityGithubActionsContent, +-} from '@backstage/plugin-github-actions'; + import { +@@ -73,2 +56,7 @@ import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; + ++import { ++ EntityKubernetesContent, ++ isKubernetesAvailable, ++} from '@backstage/plugin-kubernetes'; ++ + const techdocsContent = ( +@@ -85,5 +73,9 @@ const cicdContent = ( + +- +- +- ++ {/* ++ Here you can add support for different CI/CD services, for example ++ using @backstage-community/plugin-github-actions as follows: ++ ++ ++ ++ */} + +@@ -118,2 +110,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -157,2 +157,10 @@ const serviceEntityPage = ( + ++ ++ ++ ++ + +@@ -195,2 +203,10 @@ const websiteEntityPage = ( + ++ ++ ++ ++ + +@@ -307,5 +323,8 @@ const groupPage = ( + +- ++ + + ++ ++ ++ + +@@ -326,3 +345,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/app/src/index.tsx b/packages/app/src/index.tsx +index b16aaf7..d875c77 100644 +--- a/packages/app/src/index.tsx ++++ b/packages/app/src/index.tsx +@@ -2,5 +2,5 @@ import '@backstage/cli/asset-types'; + import React from 'react'; +-import ReactDOM from 'react-dom'; ++import ReactDOM from 'react-dom/client'; + import App from './App'; + +-ReactDOM.render(, document.getElementById('root')); ++ReactDOM.createRoot(document.getElementById('root')!).render(); +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index a5773aa..bef67b5 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,13 @@ 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 ++ ++# This will include the examples, if you don't need these simply remove this line ++COPY --chown=node:node examples ./examples + + # 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..6a14abc 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -18,23 +18,31 @@ + "dependencies": { ++ "@backstage/backend-common": "^0.24.0", ++ "@backstage/backend-defaults": "^0.4.2", ++ "@backstage/backend-tasks": "^0.6.0", ++ "@backstage/config": "^1.2.0", ++ "@backstage/plugin-app-backend": "^0.3.72", ++ "@backstage/plugin-auth-backend": "^0.22.10", ++ "@backstage/plugin-auth-backend-module-github-provider": "^0.1.20", ++ "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.9", ++ "@backstage/plugin-auth-node": "^0.5.0", ++ "@backstage/plugin-catalog-backend": "^1.25.0", ++ "@backstage/plugin-catalog-backend-module-logs": "^0.0.2", ++ "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.21", ++ "@backstage/plugin-kubernetes-backend": "^0.18.4", ++ "@backstage/plugin-permission-backend": "^0.5.47", ++ "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.1.20", ++ "@backstage/plugin-permission-common": "^0.8.1", ++ "@backstage/plugin-permission-node": "^0.8.1", ++ "@backstage/plugin-proxy-backend": "^0.5.4", ++ "@backstage/plugin-scaffolder-backend": "^1.24.0", ++ "@backstage/plugin-search-backend": "^1.5.15", ++ "@backstage/plugin-search-backend-module-catalog": "^0.2.0", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.33", ++ "@backstage/plugin-search-backend-module-techdocs": "^0.2.0", ++ "@backstage/plugin-search-backend-node": "^1.3.0", ++ "@backstage/plugin-techdocs-backend": "^1.10.10", + "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", +- "dockerode": "^3.3.1", +- "express": "^4.17.1", +- "express-promise-router": "^4.1.0", +- "pg": "^8.3.0", ++ "better-sqlite3": "^9.0.0", ++ "node-gyp": "^10.0.0", ++ "pg": "^8.11.3", + "winston": "^3.2.1" +@@ -42,8 +50,6 @@ + "devDependencies": { +- "@backstage/cli": "^0.17.2", +- "@types/dockerode": "^3.3.0", +- "@types/express-serve-static-core": "^4.17.5", ++ "@backstage/cli": "^0.27.0", + "@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.test.ts b/packages/backend/src/index.test.ts +deleted file mode 100644 +index 7814b8c..0000000 +--- a/packages/backend/src/index.test.ts ++++ /dev/null +@@ -1,8 +0,0 @@ +-import { PluginEnvironment } from './types'; +- +-describe('test', () => { +- it('unbreaks the test runner', () => { +- const unbreaker = {} as PluginEnvironment; +- expect(unbreaker).toBeTruthy(); +- }); +-}); +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 70bc66b..f57d4c5 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -8,102 +8,47 @@ + +-import Router from 'express-promise-router'; +-import { +- createServiceBuilder, +- loadBackendConfig, +- getRootLogger, +- useHotMemoize, +- notFoundHandler, +- CacheManager, +- DatabaseManager, +- SingleHostDiscovery, +- UrlReaders, +- ServerTokenManager, +-} from '@backstage/backend-common'; +-import { TaskScheduler } from '@backstage/backend-tasks'; +-import { Config } from '@backstage/config'; +-import app from './plugins/app'; +-import auth from './plugins/auth'; +-import catalog from './plugins/catalog'; +-import scaffolder from './plugins/scaffolder'; +-import proxy from './plugins/proxy'; +-import techdocs from './plugins/techdocs'; +-import search from './plugins/search'; +-import { PluginEnvironment } from './types'; +-import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { createBackend } from '@backstage/backend-defaults'; + +-function makeCreateEnv(config: Config) { +- const root = getRootLogger(); +- const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); +- const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); +- const permissions = ServerPermissionClient.fromConfig(config, { +- discovery, +- tokenManager, +- }); ++const backend = createBackend(); + +- root.info(`Created UrlReader ${reader}`); ++backend.add(import('@backstage/plugin-app-backend/alpha')); ++backend.add(import('@backstage/plugin-proxy-backend/alpha')); ++backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); ++backend.add(import('@backstage/plugin-techdocs-backend/alpha')); + +- return (plugin: string): PluginEnvironment => { +- const logger = root.child({ type: 'plugin', plugin }); +- const database = databaseManager.forPlugin(plugin); +- const cache = cacheManager.forPlugin(plugin); +- const scheduler = taskScheduler.forPlugin(plugin); +- return { +- logger, +- database, +- cache, +- config, +- reader, +- discovery, +- tokenManager, +- scheduler, +- permissions, +- }; +- }; +-} ++// auth plugin ++backend.add(import('@backstage/plugin-auth-backend')); ++// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin ++backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); ++// See https://backstage.io/docs/auth/guest/provider + +-async function main() { +- const config = await loadBackendConfig({ +- argv: process.argv, +- logger: getRootLogger(), +- }); +- const createEnv = makeCreateEnv(config); ++// catalog plugin ++backend.add(import('@backstage/plugin-catalog-backend/alpha')); ++backend.add( ++ import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ++); + +- const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); +- const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); +- const authEnv = useHotMemoize(module, () => createEnv('auth')); +- const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); +- const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); +- const searchEnv = useHotMemoize(module, () => createEnv('search')); +- const appEnv = useHotMemoize(module, () => createEnv('app')); ++// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors ++backend.add(import('@backstage/plugin-catalog-backend-module-logs')); + +- const apiRouter = Router(); +- apiRouter.use('/catalog', await catalog(catalogEnv)); +- apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); +- apiRouter.use('/auth', await auth(authEnv)); +- apiRouter.use('/techdocs', await techdocs(techdocsEnv)); +- apiRouter.use('/proxy', await proxy(proxyEnv)); +- apiRouter.use('/search', await search(searchEnv)); ++// permission plugin ++backend.add(import('@backstage/plugin-permission-backend/alpha')); ++// See https://backstage.io/docs/permissions/getting-started for how to create your own permission policy ++backend.add( ++ import('@backstage/plugin-permission-backend-module-allow-all-policy'), ++); + +- // Add backends ABOVE this line; this 404 handler is the catch-all fallback +- apiRouter.use(notFoundHandler()); ++// search plugin ++backend.add(import('@backstage/plugin-search-backend/alpha')); + +- const service = createServiceBuilder(module) +- .loadConfig(config) +- .addRouter('/api', apiRouter) +- .addRouter('', await app(appEnv)); ++// search engine ++// See https://backstage.io/docs/features/search/search-engines ++backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); + +- await service.start().catch(err => { +- console.log(err); +- process.exit(1); +- }); +-} ++// search collators ++backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); ++backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); + +-module.hot?.accept(); +-main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); +- process.exit(1); +-}); ++// kubernetes ++backend.add(import('@backstage/plugin-kubernetes-backend/alpha')); ++ ++backend.start(); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +deleted file mode 100644 +index 7c37f68..0000000 +--- a/packages/backend/src/plugins/app.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { createRouter } from '@backstage/plugin-app-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- return await createRouter({ +- 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 +deleted file mode 100644 +index 159116d..0000000 +--- a/packages/backend/src/plugins/auth.ts ++++ /dev/null +@@ -1,54 +0,0 @@ +-import { +- createRouter, +- providers, +- defaultAuthProviderFactories, +-} from '@backstage/plugin-auth-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-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 +- // 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 +deleted file mode 100644 +index 876cb6b..0000000 +--- a/packages/backend/src/plugins/catalog.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +-import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const builder = await CatalogBuilder.create(env); +- builder.addProcessor(new ScaffolderEntitiesProcessor()); +- const { processingEngine, router } = await builder.build(); +- await processingEngine.start(); +- return router; +-} +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +deleted file mode 100644 +index 54ec393..0000000 +--- a/packages/backend/src/plugins/proxy.ts ++++ /dev/null +@@ -1,13 +0,0 @@ +-import { createRouter } from '@backstage/plugin-proxy-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-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 +deleted file mode 100644 +index 7ce5fcf..0000000 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ /dev/null +@@ -1,20 +0,0 @@ +-import { CatalogClient } from '@backstage/catalog-client'; +-import { createRouter } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import type { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const catalogClient = new CatalogClient({ +- discoveryApi: env.discovery, +- }); +- +- return await createRouter({ +- logger: env.logger, +- config: env.config, +- database: env.database, +- reader: env.reader, +- catalogClient, +- }); +-} +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +deleted file mode 100644 +index e9469dc..0000000 +--- a/packages/backend/src/plugins/search.ts ++++ /dev/null +@@ -1,66 +0,0 @@ +-import { useHotCleanup } from '@backstage/backend-common'; +-import { createRouter } from '@backstage/plugin-search-backend'; +-import { +- IndexBuilder, +- LunrSearchEngine, +-} from '@backstage/plugin-search-backend-node'; +-import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; +-import { Router } from 'express'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- // Initialize a connection to a search engine. +- 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 +- // 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({ +- schedule, +- factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { +- discovery: env.discovery, +- logger: env.logger, +- tokenManager: env.tokenManager, +- }), +- }); +- +- // The scheduler controls when documents are gathered from collators and sent +- // to the search engine for indexing. +- const { scheduler } = await indexBuilder.build(); +- scheduler.start(); +- +- useHotCleanup(module, () => scheduler.stop()); +- +- return await createRouter({ +- engine: indexBuilder.getSearchEngine(), +- 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 +deleted file mode 100644 +index be8bb0c..0000000 +--- a/packages/backend/src/plugins/techdocs.ts ++++ /dev/null +@@ -1,51 +0,0 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; +-import { +- createRouter, +- Generators, +- Preparers, +- Publisher, +-} from '@backstage/plugin-techdocs-backend'; +-import Docker from 'dockerode'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(env.config, { +- logger: env.logger, +- reader: env.reader, +- }); +- +- // Docker client (conditionally) used by the generators, based on techdocs.generators config. +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- +- // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(env.config, { +- logger: env.logger, +- containerRunner, +- }); +- +- // Publisher is used for +- // 1. Publishing generated files to storage +- // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(env.config, { +- logger: env.logger, +- discovery: env.discovery, +- }); +- +- // checks if the publisher is working and logs the result +- await publisher.getReadiness(); +- +- return await createRouter({ +- preparers, +- generators, +- publisher, +- 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 +deleted file mode 100644 +index 8e0a864..0000000 +--- a/packages/backend/src/types.ts ++++ /dev/null +@@ -1,23 +0,0 @@ +-import { Logger } from 'winston'; +-import { Config } from '@backstage/config'; +-import { +- PluginCacheManager, +- PluginDatabaseManager, +- PluginEndpointDiscovery, +- TokenManager, +- UrlReader, +-} from '@backstage/backend-common'; +-import { PluginTaskScheduler } from '@backstage/backend-tasks'; +-import { PermissionEvaluator } from '@backstage/plugin-permission-common'; +- +-export type PluginEnvironment = { +- logger: Logger; +- database: PluginDatabaseManager; +- cache: PluginCacheManager; +- config: Config; +- reader: UrlReader; +- discovery: PluginEndpointDiscovery; +- tokenManager: TokenManager; +- scheduler: PluginTaskScheduler; +- permissions: PermissionEvaluator; +-}; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..733be13 +--- /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 dev', ++ 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..c37fc61 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1,3 @@ ++# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. ++# yarn lockfile v1 ++ diff --git a/diffs/0.4.29..0.5.18.diff b/diffs/0.4.29..0.5.18.diff new file mode 100644 index 00000000..98c7f834 --- /dev/null +++ b/diffs/0.4.29..0.5.18.diff @@ -0,0 +1,1597 @@ +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..5d426f5 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -11,12 +11,7 @@ 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' + +- # config options: https://node-postgres.com/api/client ++ # config options: https://node-postgres.com/apis/client + database: +@@ -35,6 +30,26 @@ backend: + ++auth: ++ providers: ++ guest: {} ++ + 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. +- locations: [] ++ locations: ++ # Local example data, replace this with your production config, these are intended for demo use only. ++ # File locations are relative to the backend process, typically in a deployed context, such as in a Docker container, this will be the root ++ - type: file ++ target: ./examples/entities.yaml ++ ++ # Local example template ++ - type: file ++ target: ./examples/template/template.yaml ++ rules: ++ - allow: [Template] ++ ++ # Local example organizational data ++ - type: file ++ target: ./examples/org.yaml ++ rules: ++ - allow: [User, Group] +diff --git a/app-config.yaml b/app-config.yaml +index cd6998d..ca52ec5 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 +@@ -41,3 +38,3 @@ integrations: + # 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 ++ # about setting up the GitHub integration here: https://backstage.io/docs/integrations/github/locations#configuration + token: ${GITHUB_TOKEN} +@@ -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 + +@@ -66,3 +66,5 @@ auth: + # see https://backstage.io/docs/auth/ to learn about auth providers +- providers: {} ++ providers: ++ # See https://backstage.io/docs/auth/guest/provider ++ guest: {} + +@@ -103 +105,9 @@ catalog: + # - allow: [User, Group] ++ ++kubernetes: ++ # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options ++ ++# see https://backstage.io/docs/permissions/getting-started for more on the permission framework ++permission: ++ # setting this to `false` will disable permissions ++ enabled: true +diff --git a/backstage.json b/backstage.json +index d19a958..230d39d 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "1.4.0" ++ "version": "1.30.0" + } +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..80a963e 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,13 +33,15 @@ + "devDependencies": { +- "@backstage/cli": "^0.18.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/e2e-test-utils": "^0.1.1", ++ "@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": "^10.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.6.4", +- "node-gyp": "^9.0.0" ++ "typescript": "~5.4.0" + }, + "resolutions": { +- "@types/react": "^17", +- "@types/react-dom": "^17" ++ "@types/react": "^18", ++ "@types/react-dom": "^18" + }, +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..839ff88 +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,27 @@ ++/* ++ * 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('/'); ++ ++ const enterButton = page.getByRole('button', { name: 'Enter' }); ++ await expect(enterButton).toBeVisible(); ++ await enterButton.click(); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index d88156a..346ffc1 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -8,28 +8,34 @@ + }, ++ "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.5.10", ++ "@backstage/catalog-model": "^1.6.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/core-app-api": "^1.14.2", ++ "@backstage/core-components": "^0.14.10", ++ "@backstage/core-plugin-api": "^1.9.3", ++ "@backstage/integration-react": "^1.1.30", ++ "@backstage/plugin-api-docs": "^0.11.8", ++ "@backstage/plugin-catalog": "^1.22.0", ++ "@backstage/plugin-catalog-common": "^1.0.26", ++ "@backstage/plugin-catalog-graph": "^0.4.8", ++ "@backstage/plugin-catalog-import": "^0.12.2", ++ "@backstage/plugin-catalog-react": "^1.12.3", ++ "@backstage/plugin-kubernetes": "^0.11.13", ++ "@backstage/plugin-org": "^0.6.28", ++ "@backstage/plugin-permission-react": "^0.4.25", ++ "@backstage/plugin-scaffolder": "^1.24.0", ++ "@backstage/plugin-search": "^1.4.15", ++ "@backstage/plugin-search-react": "^1.7.14", ++ "@backstage/plugin-techdocs": "^1.10.8", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.13", ++ "@backstage/plugin-techdocs-react": "^1.2.7", ++ "@backstage/plugin-user-settings": "^0.8.11", ++ "@backstage/theme": "^0.5.6", + "@material-ui/core": "^4.12.2", +@@ -37,31 +43,17 @@ + "history": "^5.0.0", +- "react": "^17.0.2", +- "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": "^18.0.2", ++ "react-dom": "^18.0.2", ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^1.1.2", +- "@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", ++ "@backstage/test-utils": "^1.5.10", ++ "@playwright/test": "^1.32.3", ++ "@testing-library/jest-dom": "^6.0.0", ++ "@testing-library/react": "^14.0.0", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^9.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..18da7c4 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -8,5 +8,4 @@ + name="description" +- content="Backstage is an open platform for building developer portals" ++ content="Backstage is an open source framework for building developer portals" + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index b94cac7..ec8ba1d 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { renderWithEffects } from '@backstage/test-utils'; ++import { render, waitFor } from '@testing-library/react'; + import App from './App'; +@@ -22,4 +22,7 @@ describe('App', () => { + +- const rendered = await renderWithEffects(); +- expect(rendered.baseElement).toBeInTheDocument(); ++ const rendered = render(); ++ ++ await waitFor(() => { ++ expect(rendered.baseElement).toBeInTheDocument(); ++ }); + }); +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index c487726..e24c468 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'; +@@ -15,3 +15,2 @@ import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +-import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +@@ -29,7 +28,11 @@ import { Root } from './components/Root'; + +-import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; ++import { ++ AlertDisplay, ++ OAuthRequestDialog, ++ SignInPage, ++} 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 +45,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -48,2 +52,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -53,10 +58,10 @@ const app = createApp({ + }, ++ components: { ++ SignInPage: props => , ++ }, + }); + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -80,9 +85,8 @@ const routes = ( + } +- /> +- } ++ element={ ++ ++ ++ ++ } + /> +@@ -96,4 +100,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -103,5 +107,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..853d175 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,22 +1,5 @@ +-/* +- * 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'; + import ExtensionIcon from '@material-ui/icons/Extension'; +-import MapIcon from '@material-ui/icons/MyLocation'; + import LibraryBooks from '@material-ui/icons/LibraryBooks'; +@@ -25,3 +8,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -41,2 +23,3 @@ import { + useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -44,2 +27,4 @@ import MenuIcon from '@material-ui/icons/Menu'; + import SearchIcon from '@material-ui/icons/Search'; ++import { MyGroupsSidebarItem } from '@backstage/plugin-org'; ++import GroupIcon from '@material-ui/icons/People'; + +@@ -66,9 +51,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -90,2 +69,7 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + ++ + +@@ -96,3 +80,3 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ {/* Items in this group will be scrollable if they run out of space */} + +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..6164c76 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,7 +27,5 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +-import { +- isGithubActionsAvailable, +- EntityGithubActionsContent, +-} from '@backstage/plugin-github-actions'; + import { +@@ -73,2 +56,7 @@ import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; + ++import { ++ EntityKubernetesContent, ++ isKubernetesAvailable, ++} from '@backstage/plugin-kubernetes'; ++ + const techdocsContent = ( +@@ -85,5 +73,9 @@ const cicdContent = ( + +- +- +- ++ {/* ++ Here you can add support for different CI/CD services, for example ++ using @backstage-community/plugin-github-actions as follows: ++ ++ ++ ++ */} + +@@ -118,2 +110,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -157,2 +157,10 @@ const serviceEntityPage = ( + ++ ++ ++ ++ + +@@ -195,2 +203,10 @@ const websiteEntityPage = ( + ++ ++ ++ ++ + +@@ -307,5 +323,8 @@ const groupPage = ( + +- ++ + + ++ ++ ++ + +@@ -326,3 +345,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/app/src/index.tsx b/packages/app/src/index.tsx +index b16aaf7..d875c77 100644 +--- a/packages/app/src/index.tsx ++++ b/packages/app/src/index.tsx +@@ -2,5 +2,5 @@ import '@backstage/cli/asset-types'; + import React from 'react'; +-import ReactDOM from 'react-dom'; ++import ReactDOM from 'react-dom/client'; + import App from './App'; + +-ReactDOM.render(, document.getElementById('root')); ++ReactDOM.createRoot(document.getElementById('root')!).render(); +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index a5773aa..bef67b5 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,13 @@ 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 ++ ++# This will include the examples, if you don't need these simply remove this line ++COPY --chown=node:node examples ./examples + + # 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..6a14abc 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -18,23 +18,31 @@ + "dependencies": { ++ "@backstage/backend-common": "^0.24.0", ++ "@backstage/backend-defaults": "^0.4.2", ++ "@backstage/backend-tasks": "^0.6.0", ++ "@backstage/config": "^1.2.0", ++ "@backstage/plugin-app-backend": "^0.3.72", ++ "@backstage/plugin-auth-backend": "^0.22.10", ++ "@backstage/plugin-auth-backend-module-github-provider": "^0.1.20", ++ "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.9", ++ "@backstage/plugin-auth-node": "^0.5.0", ++ "@backstage/plugin-catalog-backend": "^1.25.0", ++ "@backstage/plugin-catalog-backend-module-logs": "^0.0.2", ++ "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.21", ++ "@backstage/plugin-kubernetes-backend": "^0.18.4", ++ "@backstage/plugin-permission-backend": "^0.5.47", ++ "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.1.20", ++ "@backstage/plugin-permission-common": "^0.8.1", ++ "@backstage/plugin-permission-node": "^0.8.1", ++ "@backstage/plugin-proxy-backend": "^0.5.4", ++ "@backstage/plugin-scaffolder-backend": "^1.24.0", ++ "@backstage/plugin-search-backend": "^1.5.15", ++ "@backstage/plugin-search-backend-module-catalog": "^0.2.0", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.33", ++ "@backstage/plugin-search-backend-module-techdocs": "^0.2.0", ++ "@backstage/plugin-search-backend-node": "^1.3.0", ++ "@backstage/plugin-techdocs-backend": "^1.10.10", + "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", +- "dockerode": "^3.3.1", +- "express": "^4.17.1", +- "express-promise-router": "^4.1.0", +- "pg": "^8.3.0", ++ "better-sqlite3": "^9.0.0", ++ "node-gyp": "^10.0.0", ++ "pg": "^8.11.3", + "winston": "^3.2.1" +@@ -42,8 +50,6 @@ + "devDependencies": { +- "@backstage/cli": "^0.18.0", +- "@types/dockerode": "^3.3.0", +- "@types/express-serve-static-core": "^4.17.5", ++ "@backstage/cli": "^0.27.0", + "@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.test.ts b/packages/backend/src/index.test.ts +deleted file mode 100644 +index 7814b8c..0000000 +--- a/packages/backend/src/index.test.ts ++++ /dev/null +@@ -1,8 +0,0 @@ +-import { PluginEnvironment } from './types'; +- +-describe('test', () => { +- it('unbreaks the test runner', () => { +- const unbreaker = {} as PluginEnvironment; +- expect(unbreaker).toBeTruthy(); +- }); +-}); +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 70bc66b..f57d4c5 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -8,102 +8,47 @@ + +-import Router from 'express-promise-router'; +-import { +- createServiceBuilder, +- loadBackendConfig, +- getRootLogger, +- useHotMemoize, +- notFoundHandler, +- CacheManager, +- DatabaseManager, +- SingleHostDiscovery, +- UrlReaders, +- ServerTokenManager, +-} from '@backstage/backend-common'; +-import { TaskScheduler } from '@backstage/backend-tasks'; +-import { Config } from '@backstage/config'; +-import app from './plugins/app'; +-import auth from './plugins/auth'; +-import catalog from './plugins/catalog'; +-import scaffolder from './plugins/scaffolder'; +-import proxy from './plugins/proxy'; +-import techdocs from './plugins/techdocs'; +-import search from './plugins/search'; +-import { PluginEnvironment } from './types'; +-import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { createBackend } from '@backstage/backend-defaults'; + +-function makeCreateEnv(config: Config) { +- const root = getRootLogger(); +- const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); +- const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); +- const permissions = ServerPermissionClient.fromConfig(config, { +- discovery, +- tokenManager, +- }); ++const backend = createBackend(); + +- root.info(`Created UrlReader ${reader}`); ++backend.add(import('@backstage/plugin-app-backend/alpha')); ++backend.add(import('@backstage/plugin-proxy-backend/alpha')); ++backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); ++backend.add(import('@backstage/plugin-techdocs-backend/alpha')); + +- return (plugin: string): PluginEnvironment => { +- const logger = root.child({ type: 'plugin', plugin }); +- const database = databaseManager.forPlugin(plugin); +- const cache = cacheManager.forPlugin(plugin); +- const scheduler = taskScheduler.forPlugin(plugin); +- return { +- logger, +- database, +- cache, +- config, +- reader, +- discovery, +- tokenManager, +- scheduler, +- permissions, +- }; +- }; +-} ++// auth plugin ++backend.add(import('@backstage/plugin-auth-backend')); ++// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin ++backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); ++// See https://backstage.io/docs/auth/guest/provider + +-async function main() { +- const config = await loadBackendConfig({ +- argv: process.argv, +- logger: getRootLogger(), +- }); +- const createEnv = makeCreateEnv(config); ++// catalog plugin ++backend.add(import('@backstage/plugin-catalog-backend/alpha')); ++backend.add( ++ import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ++); + +- const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); +- const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); +- const authEnv = useHotMemoize(module, () => createEnv('auth')); +- const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); +- const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); +- const searchEnv = useHotMemoize(module, () => createEnv('search')); +- const appEnv = useHotMemoize(module, () => createEnv('app')); ++// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors ++backend.add(import('@backstage/plugin-catalog-backend-module-logs')); + +- const apiRouter = Router(); +- apiRouter.use('/catalog', await catalog(catalogEnv)); +- apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); +- apiRouter.use('/auth', await auth(authEnv)); +- apiRouter.use('/techdocs', await techdocs(techdocsEnv)); +- apiRouter.use('/proxy', await proxy(proxyEnv)); +- apiRouter.use('/search', await search(searchEnv)); ++// permission plugin ++backend.add(import('@backstage/plugin-permission-backend/alpha')); ++// See https://backstage.io/docs/permissions/getting-started for how to create your own permission policy ++backend.add( ++ import('@backstage/plugin-permission-backend-module-allow-all-policy'), ++); + +- // Add backends ABOVE this line; this 404 handler is the catch-all fallback +- apiRouter.use(notFoundHandler()); ++// search plugin ++backend.add(import('@backstage/plugin-search-backend/alpha')); + +- const service = createServiceBuilder(module) +- .loadConfig(config) +- .addRouter('/api', apiRouter) +- .addRouter('', await app(appEnv)); ++// search engine ++// See https://backstage.io/docs/features/search/search-engines ++backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); + +- await service.start().catch(err => { +- console.log(err); +- process.exit(1); +- }); +-} ++// search collators ++backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); ++backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); + +-module.hot?.accept(); +-main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); +- process.exit(1); +-}); ++// kubernetes ++backend.add(import('@backstage/plugin-kubernetes-backend/alpha')); ++ ++backend.start(); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +deleted file mode 100644 +index 7c37f68..0000000 +--- a/packages/backend/src/plugins/app.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { createRouter } from '@backstage/plugin-app-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- return await createRouter({ +- 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 +deleted file mode 100644 +index 159116d..0000000 +--- a/packages/backend/src/plugins/auth.ts ++++ /dev/null +@@ -1,54 +0,0 @@ +-import { +- createRouter, +- providers, +- defaultAuthProviderFactories, +-} from '@backstage/plugin-auth-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-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 +- // 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 +deleted file mode 100644 +index 876cb6b..0000000 +--- a/packages/backend/src/plugins/catalog.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +-import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const builder = await CatalogBuilder.create(env); +- builder.addProcessor(new ScaffolderEntitiesProcessor()); +- const { processingEngine, router } = await builder.build(); +- await processingEngine.start(); +- return router; +-} +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +deleted file mode 100644 +index 54ec393..0000000 +--- a/packages/backend/src/plugins/proxy.ts ++++ /dev/null +@@ -1,13 +0,0 @@ +-import { createRouter } from '@backstage/plugin-proxy-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-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 +deleted file mode 100644 +index 7ce5fcf..0000000 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ /dev/null +@@ -1,20 +0,0 @@ +-import { CatalogClient } from '@backstage/catalog-client'; +-import { createRouter } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import type { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const catalogClient = new CatalogClient({ +- discoveryApi: env.discovery, +- }); +- +- return await createRouter({ +- logger: env.logger, +- config: env.config, +- database: env.database, +- reader: env.reader, +- catalogClient, +- }); +-} +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +deleted file mode 100644 +index e9469dc..0000000 +--- a/packages/backend/src/plugins/search.ts ++++ /dev/null +@@ -1,66 +0,0 @@ +-import { useHotCleanup } from '@backstage/backend-common'; +-import { createRouter } from '@backstage/plugin-search-backend'; +-import { +- IndexBuilder, +- LunrSearchEngine, +-} from '@backstage/plugin-search-backend-node'; +-import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; +-import { Router } from 'express'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- // Initialize a connection to a search engine. +- 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 +- // 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({ +- schedule, +- factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { +- discovery: env.discovery, +- logger: env.logger, +- tokenManager: env.tokenManager, +- }), +- }); +- +- // The scheduler controls when documents are gathered from collators and sent +- // to the search engine for indexing. +- const { scheduler } = await indexBuilder.build(); +- scheduler.start(); +- +- useHotCleanup(module, () => scheduler.stop()); +- +- return await createRouter({ +- engine: indexBuilder.getSearchEngine(), +- 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 +deleted file mode 100644 +index be8bb0c..0000000 +--- a/packages/backend/src/plugins/techdocs.ts ++++ /dev/null +@@ -1,51 +0,0 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; +-import { +- createRouter, +- Generators, +- Preparers, +- Publisher, +-} from '@backstage/plugin-techdocs-backend'; +-import Docker from 'dockerode'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(env.config, { +- logger: env.logger, +- reader: env.reader, +- }); +- +- // Docker client (conditionally) used by the generators, based on techdocs.generators config. +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- +- // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(env.config, { +- logger: env.logger, +- containerRunner, +- }); +- +- // Publisher is used for +- // 1. Publishing generated files to storage +- // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(env.config, { +- logger: env.logger, +- discovery: env.discovery, +- }); +- +- // checks if the publisher is working and logs the result +- await publisher.getReadiness(); +- +- return await createRouter({ +- preparers, +- generators, +- publisher, +- 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 +deleted file mode 100644 +index 8e0a864..0000000 +--- a/packages/backend/src/types.ts ++++ /dev/null +@@ -1,23 +0,0 @@ +-import { Logger } from 'winston'; +-import { Config } from '@backstage/config'; +-import { +- PluginCacheManager, +- PluginDatabaseManager, +- PluginEndpointDiscovery, +- TokenManager, +- UrlReader, +-} from '@backstage/backend-common'; +-import { PluginTaskScheduler } from '@backstage/backend-tasks'; +-import { PermissionEvaluator } from '@backstage/plugin-permission-common'; +- +-export type PluginEnvironment = { +- logger: Logger; +- database: PluginDatabaseManager; +- cache: PluginCacheManager; +- config: Config; +- reader: UrlReader; +- discovery: PluginEndpointDiscovery; +- tokenManager: TokenManager; +- scheduler: PluginTaskScheduler; +- permissions: PermissionEvaluator; +-}; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..733be13 +--- /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 dev', ++ 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..c37fc61 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1,3 @@ ++# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. ++# yarn lockfile v1 ++ diff --git a/diffs/0.4.3..0.5.18.diff b/diffs/0.4.3..0.5.18.diff new file mode 100644 index 00000000..6acc8a88 --- /dev/null +++ b/diffs/0.4.3..0.5.18.diff @@ -0,0 +1,2081 @@ +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..5d426f5 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -1,8 +1,55 @@ + 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/apis/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 ++ ++auth: ++ providers: ++ guest: {} ++ ++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: ++ # Local example data, replace this with your production config, these are intended for demo use only. ++ # File locations are relative to the backend process, typically in a deployed context, such as in a Docker container, this will be the root ++ - type: file ++ target: ./examples/entities.yaml ++ ++ # Local example template ++ - type: file ++ target: ./examples/template/template.yaml ++ rules: ++ - allow: [Template] ++ ++ # Local example organizational data ++ - type: file ++ target: ./examples/org.yaml ++ rules: ++ - allow: [User, Group] +diff --git a/app-config.yaml b/app-config.yaml +index 93b0c3f..ca52ec5 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/integrations/github/locations#configuration + 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 + +@@ -53,49 +66,48 @@ auth: + # see https://backstage.io/docs/auth/ to learn about auth providers +- providers: {} ++ providers: ++ # See https://backstage.io/docs/auth/guest/provider ++ guest: {} + + 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 ++ # 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 ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml ++ rules: ++ - allow: [Template] + +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml ++ rules: ++ - allow: [User, Group] + +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml + +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.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] + +- # 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 +- 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 +- rules: +- - allow: [Template] ++kubernetes: ++ # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options ++ ++# see https://backstage.io/docs/permissions/getting-started for more on the permission framework ++permission: ++ # setting this to `false` will disable permissions ++ enabled: true +diff --git a/backstage.json b/backstage.json +new file mode 100644 +index 0000000..230d39d +--- /dev/null ++++ b/backstage.json +@@ -0,0 +1,3 @@ ++{ ++ "version": "1.30.0" ++} +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..80a963e 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.27.0", ++ "@backstage/e2e-test-utils": "^0.1.1", ++ "@playwright/test": "^1.32.3", ++ "@spotify/prettier-config": "^12.0.0", ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^10.0.0", ++ "prettier": "^2.3.2", ++ "typescript": "~5.4.0" ++ }, ++ "resolutions": { ++ "@types/react": "^18", ++ "@types/react-dom": "^18" + }, +@@ -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..839ff88 +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,27 @@ ++/* ++ * 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('/'); ++ ++ const enterButton = page.getByRole('button', { name: 'Enter' }); ++ await expect(enterButton).toBeVisible(); ++ await enterButton.click(); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index b585e49..346ffc1 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,22 +5,37 @@ + "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.5.10", ++ "@backstage/catalog-model": "^1.6.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/core-app-api": "^1.14.2", ++ "@backstage/core-components": "^0.14.10", ++ "@backstage/core-plugin-api": "^1.9.3", ++ "@backstage/integration-react": "^1.1.30", ++ "@backstage/plugin-api-docs": "^0.11.8", ++ "@backstage/plugin-catalog": "^1.22.0", ++ "@backstage/plugin-catalog-common": "^1.0.26", ++ "@backstage/plugin-catalog-graph": "^0.4.8", ++ "@backstage/plugin-catalog-import": "^0.12.2", ++ "@backstage/plugin-catalog-react": "^1.12.3", ++ "@backstage/plugin-kubernetes": "^0.11.13", ++ "@backstage/plugin-org": "^0.6.28", ++ "@backstage/plugin-permission-react": "^0.4.25", ++ "@backstage/plugin-scaffolder": "^1.24.0", ++ "@backstage/plugin-search": "^1.4.15", ++ "@backstage/plugin-search-react": "^1.7.14", ++ "@backstage/plugin-techdocs": "^1.10.8", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.13", ++ "@backstage/plugin-techdocs-react": "^1.2.7", ++ "@backstage/plugin-user-settings": "^0.8.11", ++ "@backstage/theme": "^0.5.6", + "@material-ui/core": "^4.12.2", +@@ -28,30 +43,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": "^18.0.2", ++ "react-dom": "^18.0.2", ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@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", ++ "@backstage/test-utils": "^1.5.10", ++ "@playwright/test": "^1.32.3", ++ "@testing-library/jest-dom": "^6.0.0", ++ "@testing-library/react": "^14.0.0", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^9.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..18da7c4 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -8,5 +8,4 @@ + name="description" +- content="Backstage is an open platform for building developer portals" ++ content="Backstage is an open source framework for building developer portals" + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index 82bc479..ec8ba1d 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { renderWithEffects } from '@backstage/test-utils'; ++import { render, waitFor } from '@testing-library/react'; + import App from './App'; +@@ -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', + }, +@@ -22,4 +22,7 @@ describe('App', () => { + +- const rendered = await renderWithEffects(); +- expect(rendered.baseElement).toBeInTheDocument(); ++ const rendered = render(); ++ ++ await waitFor(() => { ++ expect(rendered.baseElement).toBeInTheDocument(); ++ }); + }); +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 4cd8368..e24c468 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,6 +13,5 @@ import { + import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; ++import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +-import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -21,2 +20,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'; +@@ -27,4 +28,12 @@ import { Root } from './components/Root'; + +-import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; +-import { createApp, FlatRoutes } from '@backstage/core-app-api'; ++import { ++ AlertDisplay, ++ OAuthRequestDialog, ++ SignInPage, ++} from '@backstage/core-components'; ++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 +45,6 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); + bind(apiDocsPlugin.externalRoutes, { +- createComponent: scaffolderPlugin.routes.root, ++ registerApi: catalogImportPlugin.routes.importPage, + }); +@@ -42,3 +52,10 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); ++ bind(orgPlugin.externalRoutes, { ++ catalogIndex: catalogPlugin.routes.catalogIndex, ++ }); ++ }, ++ components: { ++ SignInPage: props => , + }, +@@ -46,8 +63,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -59,5 +73,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -69,6 +85,9 @@ const routes = ( + } ++ path="/catalog-import" ++ element={ ++ ++ ++ ++ } + /> +- } /> + }> +@@ -77,2 +96,3 @@ const routes = ( + } /> ++ } /> + +@@ -80,4 +100,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -87,5 +107,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..853d175 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,22 +1,5 @@ +-/* +- * 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'; + import ExtensionIcon from '@material-ui/icons/Extension'; +-import MapIcon from '@material-ui/icons/MyLocation'; + import LibraryBooks from '@material-ui/icons/LibraryBooks'; +@@ -25,4 +8,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 +15,16 @@ 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'; ++import { MyGroupsSidebarItem } from '@backstage/plugin-org'; ++import GroupIcon from '@material-ui/icons/People'; + +@@ -56,3 +47,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -60,8 +51,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -76,17 +62,32 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ } to="/search"> ++ ++ + +- {/* Global nav, not org-specific */} +- +- +- +- +- {/* End global nav */} +- +- +- +- ++ }> ++ {/* Global nav, not org-specific */} ++ ++ ++ ++ ++ ++ {/* End global nav */} ++ ++ ++ {/* Items in this group will be scrollable if they run out of space */} ++ ++ + + +- ++ } ++ 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..6164c76 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,7 +27,5 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +-import { +- isGithubActionsAvailable, +- EntityGithubActionsContent, +-} from '@backstage/plugin-github-actions'; + import { +@@ -56,2 +38,32 @@ 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'; ++ ++import { ++ EntityKubernetesContent, ++ isKubernetesAvailable, ++} from '@backstage/plugin-kubernetes'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); + +@@ -61,5 +73,9 @@ const cicdContent = ( + +- +- +- ++ {/* ++ Here you can add support for different CI/CD services, for example ++ using @backstage-community/plugin-github-actions as follows: ++ ++ ++ ++ */} + +@@ -94,2 +110,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -110,2 +134,6 @@ const overviewContent = ( + ++ ++ ++ ++ + +@@ -129,2 +157,10 @@ const serviceEntityPage = ( + ++ ++ ++ ++ + +@@ -152,3 +188,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -167,2 +203,10 @@ const websiteEntityPage = ( + ++ ++ ++ ++ + +@@ -179,3 +223,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -198,3 +242,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -225,2 +269,5 @@ const apiPage = ( + ++ ++ ++ + +@@ -276,5 +323,8 @@ const groupPage = ( + +- ++ + + ++ ++ ++ + +@@ -292,3 +342,9 @@ const systemPage = ( + +- ++ ++ ++ ++ ++ ++ ++ + +@@ -304,3 +360,19 @@ const systemPage = ( + +- ++ + +@@ -317,2 +389,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/app/src/index.tsx b/packages/app/src/index.tsx +index b16aaf7..d875c77 100644 +--- a/packages/app/src/index.tsx ++++ b/packages/app/src/index.tsx +@@ -2,5 +2,5 @@ import '@backstage/cli/asset-types'; + import React from 'react'; +-import ReactDOM from 'react-dom'; ++import ReactDOM from 'react-dom/client'; + import App from './App'; + +-ReactDOM.render(, document.getElementById('root')); ++ReactDOM.createRoot(document.getElementById('root')!).render(); +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..bef67b5 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,15 @@ 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 ++ ++# This will include the examples, if you don't need these simply remove this line ++COPY --chown=node:node examples ./examples + + # 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..6a14abc 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,32 +6,43 @@ + "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", +- "express": "^4.17.1", +- "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "@backstage/backend-common": "^0.24.0", ++ "@backstage/backend-defaults": "^0.4.2", ++ "@backstage/backend-tasks": "^0.6.0", ++ "@backstage/config": "^1.2.0", ++ "@backstage/plugin-app-backend": "^0.3.72", ++ "@backstage/plugin-auth-backend": "^0.22.10", ++ "@backstage/plugin-auth-backend-module-github-provider": "^0.1.20", ++ "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.9", ++ "@backstage/plugin-auth-node": "^0.5.0", ++ "@backstage/plugin-catalog-backend": "^1.25.0", ++ "@backstage/plugin-catalog-backend-module-logs": "^0.0.2", ++ "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.21", ++ "@backstage/plugin-kubernetes-backend": "^0.18.4", ++ "@backstage/plugin-permission-backend": "^0.5.47", ++ "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.1.20", ++ "@backstage/plugin-permission-common": "^0.8.1", ++ "@backstage/plugin-permission-node": "^0.8.1", ++ "@backstage/plugin-proxy-backend": "^0.5.4", ++ "@backstage/plugin-scaffolder-backend": "^1.24.0", ++ "@backstage/plugin-search-backend": "^1.5.15", ++ "@backstage/plugin-search-backend-module-catalog": "^0.2.0", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.33", ++ "@backstage/plugin-search-backend-module-techdocs": "^0.2.0", ++ "@backstage/plugin-search-backend-node": "^1.3.0", ++ "@backstage/plugin-techdocs-backend": "^1.10.10", ++ "app": "link:../app", ++ "better-sqlite3": "^9.0.0", ++ "node-gyp": "^10.0.0", ++ "pg": "^8.11.3", + "winston": "^3.2.1" +@@ -39,6 +50,6 @@ + "devDependencies": { +- "@backstage/cli": "^0.8.2", +- "@types/dockerode": "^3.2.1", ++ "@backstage/cli": "^0.27.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.test.ts b/packages/backend/src/index.test.ts +deleted file mode 100644 +index 7814b8c..0000000 +--- a/packages/backend/src/index.test.ts ++++ /dev/null +@@ -1,8 +0,0 @@ +-import { PluginEnvironment } from './types'; +- +-describe('test', () => { +- it('unbreaks the test runner', () => { +- const unbreaker = {} as PluginEnvironment; +- expect(unbreaker).toBeTruthy(); +- }); +-}); +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index f2b14b2..f57d4c5 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -8,81 +8,47 @@ + +-import Router from 'express-promise-router'; +-import { +- createServiceBuilder, +- loadBackendConfig, +- getRootLogger, +- useHotMemoize, +- notFoundHandler, +- CacheManager, +- DatabaseManager, +- SingleHostDiscovery, +- UrlReaders, +-} from '@backstage/backend-common'; +-import { Config } from '@backstage/config'; +-import app from './plugins/app'; +-import auth from './plugins/auth'; +-import catalog from './plugins/catalog'; +-import scaffolder from './plugins/scaffolder'; +-import proxy from './plugins/proxy'; +-import techdocs from './plugins/techdocs'; +-import search from './plugins/search'; +-import { PluginEnvironment } from './types'; ++import { createBackend } from '@backstage/backend-defaults'; + +-function makeCreateEnv(config: Config) { +- const root = getRootLogger(); +- const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++const backend = createBackend(); + +- root.info(`Created UrlReader ${reader}`); ++backend.add(import('@backstage/plugin-app-backend/alpha')); ++backend.add(import('@backstage/plugin-proxy-backend/alpha')); ++backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); ++backend.add(import('@backstage/plugin-techdocs-backend/alpha')); + +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++// auth plugin ++backend.add(import('@backstage/plugin-auth-backend')); ++// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin ++backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); ++// See https://backstage.io/docs/auth/guest/provider + +- return (plugin: string): PluginEnvironment => { +- const logger = root.child({ type: 'plugin', plugin }); +- const database = databaseManager.forPlugin(plugin); +- const cache = cacheManager.forPlugin(plugin); +- return { logger, database, cache, config, reader, discovery }; +- }; +-} ++// catalog plugin ++backend.add(import('@backstage/plugin-catalog-backend/alpha')); ++backend.add( ++ import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ++); + +-async function main() { +- const config = await loadBackendConfig({ +- argv: process.argv, +- logger: getRootLogger(), +- }); +- const createEnv = makeCreateEnv(config); ++// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors ++backend.add(import('@backstage/plugin-catalog-backend-module-logs')); + +- const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); +- const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); +- const authEnv = useHotMemoize(module, () => createEnv('auth')); +- const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); +- const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); +- const searchEnv = useHotMemoize(module, () => createEnv('search')); +- const appEnv = useHotMemoize(module, () => createEnv('app')); ++// permission plugin ++backend.add(import('@backstage/plugin-permission-backend/alpha')); ++// See https://backstage.io/docs/permissions/getting-started for how to create your own permission policy ++backend.add( ++ import('@backstage/plugin-permission-backend-module-allow-all-policy'), ++); + +- const apiRouter = Router(); +- apiRouter.use('/catalog', await catalog(catalogEnv)); +- apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); +- apiRouter.use('/auth', await auth(authEnv)); +- apiRouter.use('/techdocs', await techdocs(techdocsEnv)); +- apiRouter.use('/proxy', await proxy(proxyEnv)); +- apiRouter.use('/search', await search(searchEnv)); +- apiRouter.use(notFoundHandler()); ++// search plugin ++backend.add(import('@backstage/plugin-search-backend/alpha')); + +- const service = createServiceBuilder(module) +- .loadConfig(config) +- .addRouter('/api', apiRouter) +- .addRouter('', await app(appEnv)); ++// search engine ++// See https://backstage.io/docs/features/search/search-engines ++backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); + +- await service.start().catch(err => { +- console.log(err); +- process.exit(1); +- }); +-} ++// search collators ++backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); ++backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); + +-module.hot?.accept(); +-main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); +- process.exit(1); +-}); ++// kubernetes ++backend.add(import('@backstage/plugin-kubernetes-backend/alpha')); ++ ++backend.start(); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +deleted file mode 100644 +index 07fb04f..0000000 +--- a/packages/backend/src/plugins/app.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { createRouter } from '@backstage/plugin-app-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +-}: PluginEnvironment): Promise { +- return await createRouter({ +- logger, +- config, +- appPackageName: 'app', +- }); +-} +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +deleted file mode 100644 +index 5216510..0000000 +--- a/packages/backend/src/plugins/auth.ts ++++ /dev/null +@@ -1,12 +0,0 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, database, discovery }); +-} +diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts +deleted file mode 100644 +index 876cb6b..0000000 +--- a/packages/backend/src/plugins/catalog.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +-import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const builder = await CatalogBuilder.create(env); +- builder.addProcessor(new ScaffolderEntitiesProcessor()); +- const { processingEngine, router } = await builder.build(); +- await processingEngine.start(); +- return router; +-} +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +deleted file mode 100644 +index 506f6d9..0000000 +--- a/packages/backend/src/plugins/proxy.ts ++++ /dev/null +@@ -1,11 +0,0 @@ +-import { createRouter } from '@backstage/plugin-proxy-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); +-} +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +deleted file mode 100644 +index 6be2e97..0000000 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ /dev/null +@@ -1,27 +0,0 @@ +-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'; +-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 }); +- +- return await createRouter({ +- containerRunner, +- logger, +- config, +- database, +- catalogClient, +- reader, +- }); +-} +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +deleted file mode 100644 +index 7fc317d..0000000 +--- a/packages/backend/src/plugins/search.ts ++++ /dev/null +@@ -1,39 +0,0 @@ +-import { useHotCleanup } from '@backstage/backend-common'; +-import { createRouter } from '@backstage/plugin-search-backend'; +-import { +- IndexBuilder, +- LunrSearchEngine, +-} from '@backstage/plugin-search-backend-node'; +-import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +- +-export default async function createPlugin({ +- logger, +- discovery, +- config, +-}: PluginEnvironment) { +- // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); +- +- // Collators are responsible for gathering documents known to plugins. This +- // particular collator gathers entities from the software catalog. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { discovery }), +- }); +- +- // The scheduler controls when documents are gathered from collators and sent +- // to the search engine for indexing. +- const { scheduler } = await indexBuilder.build(); +- +- // 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()); +- +- return await createRouter({ +- engine: indexBuilder.getSearchEngine(), +- logger, +- }); +-} +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +deleted file mode 100644 +index 906d86d..0000000 +--- a/packages/backend/src/plugins/techdocs.ts ++++ /dev/null +@@ -1,53 +0,0 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; +-import { +- createRouter, +- Generators, +- Preparers, +- Publisher, +-} from '@backstage/plugin-techdocs-backend'; +-import Docker from 'dockerode'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +-}: PluginEnvironment): Promise { +- // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, +- }); +- +- // Docker client (conditionally) used by the generators, based on techdocs.generators config. +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- +- // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, +- containerRunner, +- }); +- +- // Publisher is used for +- // 1. Publishing generated files to storage +- // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, +- }); +- +- // checks if the publisher is working and logs the result +- await publisher.getReadiness(); +- +- return await createRouter({ +- preparers, +- generators, +- publisher, +- logger, +- config, +- discovery, +- }); +-} +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +deleted file mode 100644 +index 6c78a2a..0000000 +--- a/packages/backend/src/types.ts ++++ /dev/null +@@ -1,17 +0,0 @@ +-import { Logger } from 'winston'; +-import { Config } from '@backstage/config'; +-import { +- PluginCacheManager, +- PluginDatabaseManager, +- PluginEndpointDiscovery, +- UrlReader, +-} from '@backstage/backend-common'; +- +-export type PluginEnvironment = { +- logger: Logger; +- database: PluginDatabaseManager; +- cache: PluginCacheManager; +- config: Config; +- reader: UrlReader; +- discovery: PluginEndpointDiscovery; +-}; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..733be13 +--- /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 dev', ++ 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..c37fc61 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1,3 @@ ++# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. ++# yarn lockfile v1 ++ diff --git a/diffs/0.4.30..0.5.18.diff b/diffs/0.4.30..0.5.18.diff new file mode 100644 index 00000000..f0f44b5c --- /dev/null +++ b/diffs/0.4.30..0.5.18.diff @@ -0,0 +1,1587 @@ +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..5d426f5 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -11,12 +11,7 @@ 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' + +- # config options: https://node-postgres.com/api/client ++ # config options: https://node-postgres.com/apis/client + database: +@@ -35,6 +30,26 @@ backend: + ++auth: ++ providers: ++ guest: {} ++ + 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. +- locations: [] ++ locations: ++ # Local example data, replace this with your production config, these are intended for demo use only. ++ # File locations are relative to the backend process, typically in a deployed context, such as in a Docker container, this will be the root ++ - type: file ++ target: ./examples/entities.yaml ++ ++ # Local example template ++ - type: file ++ target: ./examples/template/template.yaml ++ rules: ++ - allow: [Template] ++ ++ # Local example organizational data ++ - type: file ++ target: ./examples/org.yaml ++ rules: ++ - allow: [User, Group] +diff --git a/app-config.yaml b/app-config.yaml +index 4a058de..ca52ec5 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 +@@ -41,3 +38,3 @@ integrations: + # 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 ++ # about setting up the GitHub integration here: https://backstage.io/docs/integrations/github/locations#configuration + token: ${GITHUB_TOKEN} +@@ -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 + +@@ -66,3 +66,5 @@ auth: + # see https://backstage.io/docs/auth/ to learn about auth providers +- providers: {} ++ providers: ++ # See https://backstage.io/docs/auth/guest/provider ++ guest: {} + +@@ -103 +105,9 @@ catalog: + # - allow: [User, Group] ++ ++kubernetes: ++ # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options ++ ++# see https://backstage.io/docs/permissions/getting-started for more on the permission framework ++permission: ++ # setting this to `false` will disable permissions ++ enabled: true +diff --git a/backstage.json b/backstage.json +index 08df5a2..230d39d 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "1.5.0" ++ "version": "1.30.0" + } +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..80a963e 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,13 +33,15 @@ + "devDependencies": { +- "@backstage/cli": "^0.18.1", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/e2e-test-utils": "^0.1.1", ++ "@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": "^10.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.6.4", +- "node-gyp": "^9.0.0" ++ "typescript": "~5.4.0" + }, + "resolutions": { +- "@types/react": "^17", +- "@types/react-dom": "^17" ++ "@types/react": "^18", ++ "@types/react-dom": "^18" + }, +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..839ff88 +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,27 @@ ++/* ++ * 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('/'); ++ ++ const enterButton = page.getByRole('button', { name: 'Enter' }); ++ await expect(enterButton).toBeVisible(); ++ await enterButton.click(); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 1d7aa24..346ffc1 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -8,28 +8,34 @@ + }, ++ "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.5.10", ++ "@backstage/catalog-model": "^1.6.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/core-app-api": "^1.14.2", ++ "@backstage/core-components": "^0.14.10", ++ "@backstage/core-plugin-api": "^1.9.3", ++ "@backstage/integration-react": "^1.1.30", ++ "@backstage/plugin-api-docs": "^0.11.8", ++ "@backstage/plugin-catalog": "^1.22.0", ++ "@backstage/plugin-catalog-common": "^1.0.26", ++ "@backstage/plugin-catalog-graph": "^0.4.8", ++ "@backstage/plugin-catalog-import": "^0.12.2", ++ "@backstage/plugin-catalog-react": "^1.12.3", ++ "@backstage/plugin-kubernetes": "^0.11.13", ++ "@backstage/plugin-org": "^0.6.28", ++ "@backstage/plugin-permission-react": "^0.4.25", ++ "@backstage/plugin-scaffolder": "^1.24.0", ++ "@backstage/plugin-search": "^1.4.15", ++ "@backstage/plugin-search-react": "^1.7.14", ++ "@backstage/plugin-techdocs": "^1.10.8", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.13", ++ "@backstage/plugin-techdocs-react": "^1.2.7", ++ "@backstage/plugin-user-settings": "^0.8.11", ++ "@backstage/theme": "^0.5.6", + "@material-ui/core": "^4.12.2", +@@ -37,31 +43,17 @@ + "history": "^5.0.0", +- "react": "^17.0.2", +- "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": "^18.0.2", ++ "react-dom": "^18.0.2", ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^1.1.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", ++ "@backstage/test-utils": "^1.5.10", ++ "@playwright/test": "^1.32.3", ++ "@testing-library/jest-dom": "^6.0.0", ++ "@testing-library/react": "^14.0.0", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^9.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..18da7c4 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -8,5 +8,4 @@ + name="description" +- content="Backstage is an open platform for building developer portals" ++ content="Backstage is an open source framework for building developer portals" + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index b94cac7..ec8ba1d 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { renderWithEffects } from '@backstage/test-utils'; ++import { render, waitFor } from '@testing-library/react'; + import App from './App'; +@@ -22,4 +22,7 @@ describe('App', () => { + +- const rendered = await renderWithEffects(); +- expect(rendered.baseElement).toBeInTheDocument(); ++ const rendered = render(); ++ ++ await waitFor(() => { ++ expect(rendered.baseElement).toBeInTheDocument(); ++ }); + }); +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index c487726..e24c468 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'; +@@ -15,3 +15,2 @@ import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +-import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +@@ -29,7 +28,11 @@ import { Root } from './components/Root'; + +-import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; ++import { ++ AlertDisplay, ++ OAuthRequestDialog, ++ SignInPage, ++} 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 +45,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -48,2 +52,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -53,10 +58,10 @@ const app = createApp({ + }, ++ components: { ++ SignInPage: props => , ++ }, + }); + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -80,9 +85,8 @@ const routes = ( + } +- /> +- } ++ element={ ++ ++ ++ ++ } + /> +@@ -96,4 +100,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -103,5 +107,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..853d175 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,22 +1,5 @@ +-/* +- * 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'; + import ExtensionIcon from '@material-ui/icons/Extension'; +-import MapIcon from '@material-ui/icons/MyLocation'; + import LibraryBooks from '@material-ui/icons/LibraryBooks'; +@@ -25,3 +8,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -41,2 +23,3 @@ import { + useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -44,2 +27,4 @@ import MenuIcon from '@material-ui/icons/Menu'; + import SearchIcon from '@material-ui/icons/Search'; ++import { MyGroupsSidebarItem } from '@backstage/plugin-org'; ++import GroupIcon from '@material-ui/icons/People'; + +@@ -66,9 +51,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -90,2 +69,7 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + ++ + +@@ -96,3 +80,3 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ {/* Items in this group will be scrollable if they run out of space */} + +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..6164c76 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,7 +27,5 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +-import { +- isGithubActionsAvailable, +- EntityGithubActionsContent, +-} from '@backstage/plugin-github-actions'; + import { +@@ -73,2 +56,7 @@ import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; + ++import { ++ EntityKubernetesContent, ++ isKubernetesAvailable, ++} from '@backstage/plugin-kubernetes'; ++ + const techdocsContent = ( +@@ -85,5 +73,9 @@ const cicdContent = ( + +- +- +- ++ {/* ++ Here you can add support for different CI/CD services, for example ++ using @backstage-community/plugin-github-actions as follows: ++ ++ ++ ++ */} + +@@ -118,2 +110,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -157,2 +157,10 @@ const serviceEntityPage = ( + ++ ++ ++ ++ + +@@ -195,2 +203,10 @@ const websiteEntityPage = ( + ++ ++ ++ ++ + +@@ -307,5 +323,8 @@ const groupPage = ( + +- ++ + + ++ ++ ++ + +@@ -326,3 +345,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/app/src/index.tsx b/packages/app/src/index.tsx +index b16aaf7..d875c77 100644 +--- a/packages/app/src/index.tsx ++++ b/packages/app/src/index.tsx +@@ -2,5 +2,5 @@ import '@backstage/cli/asset-types'; + import React from 'react'; +-import ReactDOM from 'react-dom'; ++import ReactDOM from 'react-dom/client'; + import App from './App'; + +-ReactDOM.render(, document.getElementById('root')); ++ReactDOM.createRoot(document.getElementById('root')!).render(); +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index 8836ac7..bef67b5 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,13 @@ 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 ++ ++# This will include the examples, if you don't need these simply remove this line ++COPY --chown=node:node examples ./examples + + # 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..6a14abc 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -18,24 +18,31 @@ + "dependencies": { ++ "@backstage/backend-common": "^0.24.0", ++ "@backstage/backend-defaults": "^0.4.2", ++ "@backstage/backend-tasks": "^0.6.0", ++ "@backstage/config": "^1.2.0", ++ "@backstage/plugin-app-backend": "^0.3.72", ++ "@backstage/plugin-auth-backend": "^0.22.10", ++ "@backstage/plugin-auth-backend-module-github-provider": "^0.1.20", ++ "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.9", ++ "@backstage/plugin-auth-node": "^0.5.0", ++ "@backstage/plugin-catalog-backend": "^1.25.0", ++ "@backstage/plugin-catalog-backend-module-logs": "^0.0.2", ++ "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.21", ++ "@backstage/plugin-kubernetes-backend": "^0.18.4", ++ "@backstage/plugin-permission-backend": "^0.5.47", ++ "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.1.20", ++ "@backstage/plugin-permission-common": "^0.8.1", ++ "@backstage/plugin-permission-node": "^0.8.1", ++ "@backstage/plugin-proxy-backend": "^0.5.4", ++ "@backstage/plugin-scaffolder-backend": "^1.24.0", ++ "@backstage/plugin-search-backend": "^1.5.15", ++ "@backstage/plugin-search-backend-module-catalog": "^0.2.0", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.33", ++ "@backstage/plugin-search-backend-module-techdocs": "^0.2.0", ++ "@backstage/plugin-search-backend-node": "^1.3.0", ++ "@backstage/plugin-techdocs-backend": "^1.10.10", + "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", +- "dockerode": "^3.3.1", +- "express": "^4.17.1", +- "express-promise-router": "^4.1.0", +- "pg": "^8.3.0", ++ "better-sqlite3": "^9.0.0", ++ "node-gyp": "^10.0.0", ++ "pg": "^8.11.3", + "winston": "^3.2.1" +@@ -43,6 +50,5 @@ + "devDependencies": { +- "@backstage/cli": "^0.18.1", +- "@types/dockerode": "^3.3.0", +- "@types/express-serve-static-core": "^4.17.5", ++ "@backstage/cli": "^0.27.0", + "@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.test.ts b/packages/backend/src/index.test.ts +deleted file mode 100644 +index 7814b8c..0000000 +--- a/packages/backend/src/index.test.ts ++++ /dev/null +@@ -1,8 +0,0 @@ +-import { PluginEnvironment } from './types'; +- +-describe('test', () => { +- it('unbreaks the test runner', () => { +- const unbreaker = {} as PluginEnvironment; +- expect(unbreaker).toBeTruthy(); +- }); +-}); +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index ef05fa5..f57d4c5 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -8,102 +8,47 @@ + +-import Router from 'express-promise-router'; +-import { +- createServiceBuilder, +- loadBackendConfig, +- getRootLogger, +- useHotMemoize, +- notFoundHandler, +- CacheManager, +- DatabaseManager, +- SingleHostDiscovery, +- UrlReaders, +- ServerTokenManager, +-} from '@backstage/backend-common'; +-import { TaskScheduler } from '@backstage/backend-tasks'; +-import { Config } from '@backstage/config'; +-import app from './plugins/app'; +-import auth from './plugins/auth'; +-import catalog from './plugins/catalog'; +-import scaffolder from './plugins/scaffolder'; +-import proxy from './plugins/proxy'; +-import techdocs from './plugins/techdocs'; +-import search from './plugins/search'; +-import { PluginEnvironment } from './types'; +-import { ServerPermissionClient } from '@backstage/plugin-permission-node'; ++import { createBackend } from '@backstage/backend-defaults'; + +-function makeCreateEnv(config: Config) { +- const root = getRootLogger(); +- const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); +- const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); +- const permissions = ServerPermissionClient.fromConfig(config, { +- discovery, +- tokenManager, +- }); ++const backend = createBackend(); + +- root.info(`Created UrlReader ${reader}`); ++backend.add(import('@backstage/plugin-app-backend/alpha')); ++backend.add(import('@backstage/plugin-proxy-backend/alpha')); ++backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); ++backend.add(import('@backstage/plugin-techdocs-backend/alpha')); + +- return (plugin: string): PluginEnvironment => { +- const logger = root.child({ type: 'plugin', plugin }); +- const database = databaseManager.forPlugin(plugin); +- const cache = cacheManager.forPlugin(plugin); +- const scheduler = taskScheduler.forPlugin(plugin); +- return { +- logger, +- database, +- cache, +- config, +- reader, +- discovery, +- tokenManager, +- scheduler, +- permissions, +- }; +- }; +-} ++// auth plugin ++backend.add(import('@backstage/plugin-auth-backend')); ++// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin ++backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); ++// See https://backstage.io/docs/auth/guest/provider + +-async function main() { +- const config = await loadBackendConfig({ +- argv: process.argv, +- logger: getRootLogger(), +- }); +- const createEnv = makeCreateEnv(config); ++// catalog plugin ++backend.add(import('@backstage/plugin-catalog-backend/alpha')); ++backend.add( ++ import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ++); + +- const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); +- const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); +- const authEnv = useHotMemoize(module, () => createEnv('auth')); +- const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); +- const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); +- const searchEnv = useHotMemoize(module, () => createEnv('search')); +- const appEnv = useHotMemoize(module, () => createEnv('app')); ++// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors ++backend.add(import('@backstage/plugin-catalog-backend-module-logs')); + +- const apiRouter = Router(); +- apiRouter.use('/catalog', await catalog(catalogEnv)); +- apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); +- apiRouter.use('/auth', await auth(authEnv)); +- apiRouter.use('/techdocs', await techdocs(techdocsEnv)); +- apiRouter.use('/proxy', await proxy(proxyEnv)); +- apiRouter.use('/search', await search(searchEnv)); ++// permission plugin ++backend.add(import('@backstage/plugin-permission-backend/alpha')); ++// See https://backstage.io/docs/permissions/getting-started for how to create your own permission policy ++backend.add( ++ import('@backstage/plugin-permission-backend-module-allow-all-policy'), ++); + +- // Add backends ABOVE this line; this 404 handler is the catch-all fallback +- apiRouter.use(notFoundHandler()); ++// search plugin ++backend.add(import('@backstage/plugin-search-backend/alpha')); + +- const service = createServiceBuilder(module) +- .loadConfig(config) +- .addRouter('/api', apiRouter) +- .addRouter('', await app(appEnv)); ++// search engine ++// See https://backstage.io/docs/features/search/search-engines ++backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); + +- await service.start().catch(err => { +- console.log(err); +- process.exit(1); +- }); +-} ++// search collators ++backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); ++backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); + +-module.hot?.accept(); +-main().catch(error => { +- console.error('Backend failed to start up', error); +- process.exit(1); +-}); ++// kubernetes ++backend.add(import('@backstage/plugin-kubernetes-backend/alpha')); ++ ++backend.start(); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +deleted file mode 100644 +index 7c37f68..0000000 +--- a/packages/backend/src/plugins/app.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { createRouter } from '@backstage/plugin-app-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- return await createRouter({ +- 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 +deleted file mode 100644 +index 159116d..0000000 +--- a/packages/backend/src/plugins/auth.ts ++++ /dev/null +@@ -1,54 +0,0 @@ +-import { +- createRouter, +- providers, +- defaultAuthProviderFactories, +-} from '@backstage/plugin-auth-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-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 +- // 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 +deleted file mode 100644 +index 876cb6b..0000000 +--- a/packages/backend/src/plugins/catalog.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +-import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const builder = await CatalogBuilder.create(env); +- builder.addProcessor(new ScaffolderEntitiesProcessor()); +- const { processingEngine, router } = await builder.build(); +- await processingEngine.start(); +- return router; +-} +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +deleted file mode 100644 +index 54ec393..0000000 +--- a/packages/backend/src/plugins/proxy.ts ++++ /dev/null +@@ -1,13 +0,0 @@ +-import { createRouter } from '@backstage/plugin-proxy-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-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 +deleted file mode 100644 +index 7ce5fcf..0000000 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ /dev/null +@@ -1,20 +0,0 @@ +-import { CatalogClient } from '@backstage/catalog-client'; +-import { createRouter } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import type { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const catalogClient = new CatalogClient({ +- discoveryApi: env.discovery, +- }); +- +- return await createRouter({ +- logger: env.logger, +- config: env.config, +- database: env.database, +- reader: env.reader, +- catalogClient, +- }); +-} +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +deleted file mode 100644 +index e9469dc..0000000 +--- a/packages/backend/src/plugins/search.ts ++++ /dev/null +@@ -1,66 +0,0 @@ +-import { useHotCleanup } from '@backstage/backend-common'; +-import { createRouter } from '@backstage/plugin-search-backend'; +-import { +- IndexBuilder, +- LunrSearchEngine, +-} from '@backstage/plugin-search-backend-node'; +-import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; +-import { Router } from 'express'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- // Initialize a connection to a search engine. +- 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 +- // 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({ +- schedule, +- factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { +- discovery: env.discovery, +- logger: env.logger, +- tokenManager: env.tokenManager, +- }), +- }); +- +- // The scheduler controls when documents are gathered from collators and sent +- // to the search engine for indexing. +- const { scheduler } = await indexBuilder.build(); +- scheduler.start(); +- +- useHotCleanup(module, () => scheduler.stop()); +- +- return await createRouter({ +- engine: indexBuilder.getSearchEngine(), +- 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 +deleted file mode 100644 +index be8bb0c..0000000 +--- a/packages/backend/src/plugins/techdocs.ts ++++ /dev/null +@@ -1,51 +0,0 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; +-import { +- createRouter, +- Generators, +- Preparers, +- Publisher, +-} from '@backstage/plugin-techdocs-backend'; +-import Docker from 'dockerode'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(env.config, { +- logger: env.logger, +- reader: env.reader, +- }); +- +- // Docker client (conditionally) used by the generators, based on techdocs.generators config. +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- +- // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(env.config, { +- logger: env.logger, +- containerRunner, +- }); +- +- // Publisher is used for +- // 1. Publishing generated files to storage +- // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(env.config, { +- logger: env.logger, +- discovery: env.discovery, +- }); +- +- // checks if the publisher is working and logs the result +- await publisher.getReadiness(); +- +- return await createRouter({ +- preparers, +- generators, +- publisher, +- 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 +deleted file mode 100644 +index 8e0a864..0000000 +--- a/packages/backend/src/types.ts ++++ /dev/null +@@ -1,23 +0,0 @@ +-import { Logger } from 'winston'; +-import { Config } from '@backstage/config'; +-import { +- PluginCacheManager, +- PluginDatabaseManager, +- PluginEndpointDiscovery, +- TokenManager, +- UrlReader, +-} from '@backstage/backend-common'; +-import { PluginTaskScheduler } from '@backstage/backend-tasks'; +-import { PermissionEvaluator } from '@backstage/plugin-permission-common'; +- +-export type PluginEnvironment = { +- logger: Logger; +- database: PluginDatabaseManager; +- cache: PluginCacheManager; +- config: Config; +- reader: UrlReader; +- discovery: PluginEndpointDiscovery; +- tokenManager: TokenManager; +- scheduler: PluginTaskScheduler; +- permissions: PermissionEvaluator; +-}; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..733be13 +--- /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 dev', ++ 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..c37fc61 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1,3 @@ ++# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. ++# yarn lockfile v1 ++ diff --git a/diffs/0.4.31..0.5.18.diff b/diffs/0.4.31..0.5.18.diff new file mode 100644 index 00000000..a5bf1cf3 --- /dev/null +++ b/diffs/0.4.31..0.5.18.diff @@ -0,0 +1,1548 @@ +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..5d426f5 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -11,12 +11,7 @@ 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' + +- # config options: https://node-postgres.com/api/client ++ # config options: https://node-postgres.com/apis/client + database: +@@ -35,6 +30,26 @@ backend: + ++auth: ++ providers: ++ guest: {} ++ + 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. +- locations: [] ++ locations: ++ # Local example data, replace this with your production config, these are intended for demo use only. ++ # File locations are relative to the backend process, typically in a deployed context, such as in a Docker container, this will be the root ++ - type: file ++ target: ./examples/entities.yaml ++ ++ # Local example template ++ - type: file ++ target: ./examples/template/template.yaml ++ rules: ++ - allow: [Template] ++ ++ # Local example organizational data ++ - type: file ++ target: ./examples/org.yaml ++ rules: ++ - allow: [User, Group] +diff --git a/app-config.yaml b/app-config.yaml +index 4a058de..ca52ec5 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 +@@ -41,3 +38,3 @@ integrations: + # 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 ++ # about setting up the GitHub integration here: https://backstage.io/docs/integrations/github/locations#configuration + token: ${GITHUB_TOKEN} +@@ -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 + +@@ -66,3 +66,5 @@ auth: + # see https://backstage.io/docs/auth/ to learn about auth providers +- providers: {} ++ providers: ++ # See https://backstage.io/docs/auth/guest/provider ++ guest: {} + +@@ -103 +105,9 @@ catalog: + # - allow: [User, Group] ++ ++kubernetes: ++ # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options ++ ++# see https://backstage.io/docs/permissions/getting-started for more on the permission framework ++permission: ++ # setting this to `false` will disable permissions ++ enabled: true +diff --git a/backstage.json b/backstage.json +index d5d8ca8..230d39d 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "1.6.0" ++ "version": "1.30.0" + } +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..80a963e 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,13 +33,15 @@ + "devDependencies": { +- "@backstage/cli": "^0.19.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/e2e-test-utils": "^0.1.1", ++ "@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": "^10.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.6.4", +- "node-gyp": "^9.0.0" ++ "typescript": "~5.4.0" + }, + "resolutions": { +- "@types/react": "^17", +- "@types/react-dom": "^17" ++ "@types/react": "^18", ++ "@types/react-dom": "^18" + }, +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..839ff88 +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,27 @@ ++/* ++ * 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('/'); ++ ++ const enterButton = page.getByRole('button', { name: 'Enter' }); ++ await expect(enterButton).toBeVisible(); ++ await enterButton.click(); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 522fad8..346ffc1 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -8,28 +8,34 @@ + }, ++ "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.5.10", ++ "@backstage/catalog-model": "^1.6.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/core-app-api": "^1.14.2", ++ "@backstage/core-components": "^0.14.10", ++ "@backstage/core-plugin-api": "^1.9.3", ++ "@backstage/integration-react": "^1.1.30", ++ "@backstage/plugin-api-docs": "^0.11.8", ++ "@backstage/plugin-catalog": "^1.22.0", ++ "@backstage/plugin-catalog-common": "^1.0.26", ++ "@backstage/plugin-catalog-graph": "^0.4.8", ++ "@backstage/plugin-catalog-import": "^0.12.2", ++ "@backstage/plugin-catalog-react": "^1.12.3", ++ "@backstage/plugin-kubernetes": "^0.11.13", ++ "@backstage/plugin-org": "^0.6.28", ++ "@backstage/plugin-permission-react": "^0.4.25", ++ "@backstage/plugin-scaffolder": "^1.24.0", ++ "@backstage/plugin-search": "^1.4.15", ++ "@backstage/plugin-search-react": "^1.7.14", ++ "@backstage/plugin-techdocs": "^1.10.8", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.13", ++ "@backstage/plugin-techdocs-react": "^1.2.7", ++ "@backstage/plugin-user-settings": "^0.8.11", ++ "@backstage/theme": "^0.5.6", + "@material-ui/core": "^4.12.2", +@@ -37,30 +43,17 @@ + "history": "^5.0.0", +- "react": "^17.0.2", +- "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": "^18.0.2", ++ "react-dom": "^18.0.2", ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@backstage/test-utils": "^1.2.0", +- "@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", ++ "@backstage/test-utils": "^1.5.10", ++ "@playwright/test": "^1.32.3", ++ "@testing-library/jest-dom": "^6.0.0", ++ "@testing-library/react": "^14.0.0", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^9.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..18da7c4 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -8,5 +8,4 @@ + name="description" +- content="Backstage is an open platform for building developer portals" ++ content="Backstage is an open source framework for building developer portals" + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index b94cac7..ec8ba1d 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { renderWithEffects } from '@backstage/test-utils'; ++import { render, waitFor } from '@testing-library/react'; + import App from './App'; +@@ -22,4 +22,7 @@ describe('App', () => { + +- const rendered = await renderWithEffects(); +- expect(rendered.baseElement).toBeInTheDocument(); ++ const rendered = render(); ++ ++ await waitFor(() => { ++ expect(rendered.baseElement).toBeInTheDocument(); ++ }); + }); +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index c487726..e24c468 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'; +@@ -15,3 +15,2 @@ import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +-import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +@@ -29,7 +28,11 @@ import { Root } from './components/Root'; + +-import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; ++import { ++ AlertDisplay, ++ OAuthRequestDialog, ++ SignInPage, ++} 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 +45,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -48,2 +52,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -53,10 +58,10 @@ const app = createApp({ + }, ++ components: { ++ SignInPage: props => , ++ }, + }); + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -80,9 +85,8 @@ const routes = ( + } +- /> +- } ++ element={ ++ ++ ++ ++ } + /> +@@ -96,4 +100,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -103,5 +107,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..853d175 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,22 +1,5 @@ +-/* +- * 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'; + import ExtensionIcon from '@material-ui/icons/Extension'; +-import MapIcon from '@material-ui/icons/MyLocation'; + import LibraryBooks from '@material-ui/icons/LibraryBooks'; +@@ -25,3 +8,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -41,2 +23,3 @@ import { + useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -44,2 +27,4 @@ import MenuIcon from '@material-ui/icons/Menu'; + import SearchIcon from '@material-ui/icons/Search'; ++import { MyGroupsSidebarItem } from '@backstage/plugin-org'; ++import GroupIcon from '@material-ui/icons/People'; + +@@ -66,9 +51,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -90,2 +69,7 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + ++ + +@@ -96,3 +80,3 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ {/* Items in this group will be scrollable if they run out of space */} + +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..6164c76 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,7 +27,5 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +-import { +- isGithubActionsAvailable, +- EntityGithubActionsContent, +-} from '@backstage/plugin-github-actions'; + import { +@@ -73,2 +56,7 @@ import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; + ++import { ++ EntityKubernetesContent, ++ isKubernetesAvailable, ++} from '@backstage/plugin-kubernetes'; ++ + const techdocsContent = ( +@@ -85,5 +73,9 @@ const cicdContent = ( + +- +- +- ++ {/* ++ Here you can add support for different CI/CD services, for example ++ using @backstage-community/plugin-github-actions as follows: ++ ++ ++ ++ */} + +@@ -118,2 +110,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -157,2 +157,10 @@ const serviceEntityPage = ( + ++ ++ ++ ++ + +@@ -195,2 +203,10 @@ const websiteEntityPage = ( + ++ ++ ++ ++ + +@@ -307,5 +323,8 @@ const groupPage = ( + +- ++ + + ++ ++ ++ + +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/app/src/index.tsx b/packages/app/src/index.tsx +index b16aaf7..d875c77 100644 +--- a/packages/app/src/index.tsx ++++ b/packages/app/src/index.tsx +@@ -2,5 +2,5 @@ import '@backstage/cli/asset-types'; + import React from 'react'; +-import ReactDOM from 'react-dom'; ++import ReactDOM from 'react-dom/client'; + import App from './App'; + +-ReactDOM.render(, document.getElementById('root')); ++ReactDOM.createRoot(document.getElementById('root')!).render(); +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index 682798b..bef67b5 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,7 @@ 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 ++ ++# This will include the examples, if you don't need these simply remove this line ++COPY --chown=node:node examples ./examples + +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..6a14abc 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -18,25 +18,31 @@ + "dependencies": { ++ "@backstage/backend-common": "^0.24.0", ++ "@backstage/backend-defaults": "^0.4.2", ++ "@backstage/backend-tasks": "^0.6.0", ++ "@backstage/config": "^1.2.0", ++ "@backstage/plugin-app-backend": "^0.3.72", ++ "@backstage/plugin-auth-backend": "^0.22.10", ++ "@backstage/plugin-auth-backend-module-github-provider": "^0.1.20", ++ "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.9", ++ "@backstage/plugin-auth-node": "^0.5.0", ++ "@backstage/plugin-catalog-backend": "^1.25.0", ++ "@backstage/plugin-catalog-backend-module-logs": "^0.0.2", ++ "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.21", ++ "@backstage/plugin-kubernetes-backend": "^0.18.4", ++ "@backstage/plugin-permission-backend": "^0.5.47", ++ "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.1.20", ++ "@backstage/plugin-permission-common": "^0.8.1", ++ "@backstage/plugin-permission-node": "^0.8.1", ++ "@backstage/plugin-proxy-backend": "^0.5.4", ++ "@backstage/plugin-scaffolder-backend": "^1.24.0", ++ "@backstage/plugin-search-backend": "^1.5.15", ++ "@backstage/plugin-search-backend-module-catalog": "^0.2.0", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.33", ++ "@backstage/plugin-search-backend-module-techdocs": "^0.2.0", ++ "@backstage/plugin-search-backend-node": "^1.3.0", ++ "@backstage/plugin-techdocs-backend": "^1.10.10", + "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", +- "dockerode": "^3.3.1", +- "express": "^4.17.1", +- "express-promise-router": "^4.1.0", +- "pg": "^8.3.0", ++ "better-sqlite3": "^9.0.0", ++ "node-gyp": "^10.0.0", ++ "pg": "^8.11.3", + "winston": "^3.2.1" +@@ -44,6 +50,5 @@ + "devDependencies": { +- "@backstage/cli": "^0.19.0", +- "@types/dockerode": "^3.3.0", +- "@types/express-serve-static-core": "^4.17.5", ++ "@backstage/cli": "^0.27.0", + "@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.test.ts b/packages/backend/src/index.test.ts +deleted file mode 100644 +index 7814b8c..0000000 +--- a/packages/backend/src/index.test.ts ++++ /dev/null +@@ -1,8 +0,0 @@ +-import { PluginEnvironment } from './types'; +- +-describe('test', () => { +- it('unbreaks the test runner', () => { +- const unbreaker = {} as PluginEnvironment; +- expect(unbreaker).toBeTruthy(); +- }); +-}); +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index c4736a5..f57d4c5 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -8,108 +8,47 @@ + +-import Router from 'express-promise-router'; +-import { +- createServiceBuilder, +- loadBackendConfig, +- getRootLogger, +- useHotMemoize, +- notFoundHandler, +- CacheManager, +- DatabaseManager, +- SingleHostDiscovery, +- UrlReaders, +- ServerTokenManager, +-} from '@backstage/backend-common'; +-import { TaskScheduler } from '@backstage/backend-tasks'; +-import { Config } from '@backstage/config'; +-import app from './plugins/app'; +-import auth from './plugins/auth'; +-import catalog from './plugins/catalog'; +-import scaffolder from './plugins/scaffolder'; +-import proxy from './plugins/proxy'; +-import techdocs from './plugins/techdocs'; +-import search from './plugins/search'; +-import { PluginEnvironment } from './types'; +-import { ServerPermissionClient } from '@backstage/plugin-permission-node'; +-import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; ++import { createBackend } from '@backstage/backend-defaults'; + +-function makeCreateEnv(config: Config) { +- const root = getRootLogger(); +- const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); +- const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); ++const backend = createBackend(); + +- const identity = DefaultIdentityClient.create({ +- discovery, +- }); +- const permissions = ServerPermissionClient.fromConfig(config, { +- discovery, +- tokenManager, +- }); ++backend.add(import('@backstage/plugin-app-backend/alpha')); ++backend.add(import('@backstage/plugin-proxy-backend/alpha')); ++backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); ++backend.add(import('@backstage/plugin-techdocs-backend/alpha')); + +- root.info(`Created UrlReader ${reader}`); ++// auth plugin ++backend.add(import('@backstage/plugin-auth-backend')); ++// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin ++backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); ++// See https://backstage.io/docs/auth/guest/provider + +- return (plugin: string): PluginEnvironment => { +- const logger = root.child({ type: 'plugin', plugin }); +- const database = databaseManager.forPlugin(plugin); +- const cache = cacheManager.forPlugin(plugin); +- const scheduler = taskScheduler.forPlugin(plugin); +- return { +- logger, +- database, +- cache, +- config, +- reader, +- discovery, +- tokenManager, +- scheduler, +- permissions, +- identity, +- }; +- }; +-} ++// catalog plugin ++backend.add(import('@backstage/plugin-catalog-backend/alpha')); ++backend.add( ++ import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ++); + +-async function main() { +- const config = await loadBackendConfig({ +- argv: process.argv, +- logger: getRootLogger(), +- }); +- const createEnv = makeCreateEnv(config); ++// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors ++backend.add(import('@backstage/plugin-catalog-backend-module-logs')); + +- const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); +- const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); +- const authEnv = useHotMemoize(module, () => createEnv('auth')); +- const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); +- const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); +- const searchEnv = useHotMemoize(module, () => createEnv('search')); +- const appEnv = useHotMemoize(module, () => createEnv('app')); ++// permission plugin ++backend.add(import('@backstage/plugin-permission-backend/alpha')); ++// See https://backstage.io/docs/permissions/getting-started for how to create your own permission policy ++backend.add( ++ import('@backstage/plugin-permission-backend-module-allow-all-policy'), ++); + +- const apiRouter = Router(); +- apiRouter.use('/catalog', await catalog(catalogEnv)); +- apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); +- apiRouter.use('/auth', await auth(authEnv)); +- apiRouter.use('/techdocs', await techdocs(techdocsEnv)); +- apiRouter.use('/proxy', await proxy(proxyEnv)); +- apiRouter.use('/search', await search(searchEnv)); ++// search plugin ++backend.add(import('@backstage/plugin-search-backend/alpha')); + +- // Add backends ABOVE this line; this 404 handler is the catch-all fallback +- apiRouter.use(notFoundHandler()); ++// search engine ++// See https://backstage.io/docs/features/search/search-engines ++backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); + +- const service = createServiceBuilder(module) +- .loadConfig(config) +- .addRouter('/api', apiRouter) +- .addRouter('', await app(appEnv)); ++// search collators ++backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); ++backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); + +- await service.start().catch(err => { +- console.log(err); +- process.exit(1); +- }); +-} ++// kubernetes ++backend.add(import('@backstage/plugin-kubernetes-backend/alpha')); + +-module.hot?.accept(); +-main().catch(error => { +- console.error('Backend failed to start up', error); +- process.exit(1); +-}); ++backend.start(); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +deleted file mode 100644 +index 7c37f68..0000000 +--- a/packages/backend/src/plugins/app.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { createRouter } from '@backstage/plugin-app-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- return await createRouter({ +- 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 +deleted file mode 100644 +index 77eb6aa..0000000 +--- a/packages/backend/src/plugins/auth.ts ++++ /dev/null +@@ -1,54 +0,0 @@ +-import { +- createRouter, +- providers, +- defaultAuthProviderFactories, +-} from '@backstage/plugin-auth-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-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 +deleted file mode 100644 +index 876cb6b..0000000 +--- a/packages/backend/src/plugins/catalog.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +-import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const builder = await CatalogBuilder.create(env); +- builder.addProcessor(new ScaffolderEntitiesProcessor()); +- const { processingEngine, router } = await builder.build(); +- await processingEngine.start(); +- return router; +-} +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +deleted file mode 100644 +index 54ec393..0000000 +--- a/packages/backend/src/plugins/proxy.ts ++++ /dev/null +@@ -1,13 +0,0 @@ +-import { createRouter } from '@backstage/plugin-proxy-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-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 +deleted file mode 100644 +index ef46f07..0000000 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ /dev/null +@@ -1,21 +0,0 @@ +-import { CatalogClient } from '@backstage/catalog-client'; +-import { createRouter } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import type { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const catalogClient = new CatalogClient({ +- discoveryApi: env.discovery, +- }); +- +- return await createRouter({ +- logger: env.logger, +- config: env.config, +- database: env.database, +- reader: env.reader, +- catalogClient, +- identity: env.identity, +- }); +-} +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +deleted file mode 100644 +index e9469dc..0000000 +--- a/packages/backend/src/plugins/search.ts ++++ /dev/null +@@ -1,66 +0,0 @@ +-import { useHotCleanup } from '@backstage/backend-common'; +-import { createRouter } from '@backstage/plugin-search-backend'; +-import { +- IndexBuilder, +- LunrSearchEngine, +-} from '@backstage/plugin-search-backend-node'; +-import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; +-import { Router } from 'express'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- // Initialize a connection to a search engine. +- 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 +- // 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({ +- schedule, +- factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { +- discovery: env.discovery, +- logger: env.logger, +- tokenManager: env.tokenManager, +- }), +- }); +- +- // The scheduler controls when documents are gathered from collators and sent +- // to the search engine for indexing. +- const { scheduler } = await indexBuilder.build(); +- scheduler.start(); +- +- useHotCleanup(module, () => scheduler.stop()); +- +- return await createRouter({ +- engine: indexBuilder.getSearchEngine(), +- 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 +deleted file mode 100644 +index be8bb0c..0000000 +--- a/packages/backend/src/plugins/techdocs.ts ++++ /dev/null +@@ -1,51 +0,0 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; +-import { +- createRouter, +- Generators, +- Preparers, +- Publisher, +-} from '@backstage/plugin-techdocs-backend'; +-import Docker from 'dockerode'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(env.config, { +- logger: env.logger, +- reader: env.reader, +- }); +- +- // Docker client (conditionally) used by the generators, based on techdocs.generators config. +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- +- // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(env.config, { +- logger: env.logger, +- containerRunner, +- }); +- +- // Publisher is used for +- // 1. Publishing generated files to storage +- // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(env.config, { +- logger: env.logger, +- discovery: env.discovery, +- }); +- +- // checks if the publisher is working and logs the result +- await publisher.getReadiness(); +- +- return await createRouter({ +- preparers, +- generators, +- publisher, +- 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 +deleted file mode 100644 +index 9cd2c74..0000000 +--- a/packages/backend/src/types.ts ++++ /dev/null +@@ -1,25 +0,0 @@ +-import { Logger } from 'winston'; +-import { Config } from '@backstage/config'; +-import { +- PluginCacheManager, +- PluginDatabaseManager, +- 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'; +- +-export type PluginEnvironment = { +- logger: Logger; +- database: PluginDatabaseManager; +- cache: PluginCacheManager; +- config: Config; +- reader: UrlReader; +- 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..733be13 +--- /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 dev', ++ 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..c37fc61 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1,3 @@ ++# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. ++# yarn lockfile v1 ++ diff --git a/diffs/0.4.32..0.5.18.diff b/diffs/0.4.32..0.5.18.diff new file mode 100644 index 00000000..5a362706 --- /dev/null +++ b/diffs/0.4.32..0.5.18.diff @@ -0,0 +1,1389 @@ +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..5d426f5 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -11,12 +11,7 @@ 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' + +- # config options: https://node-postgres.com/api/client ++ # config options: https://node-postgres.com/apis/client + database: +@@ -35,6 +30,26 @@ backend: + ++auth: ++ providers: ++ guest: {} ++ + 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. +- locations: [] ++ locations: ++ # Local example data, replace this with your production config, these are intended for demo use only. ++ # File locations are relative to the backend process, typically in a deployed context, such as in a Docker container, this will be the root ++ - type: file ++ target: ./examples/entities.yaml ++ ++ # Local example template ++ - type: file ++ target: ./examples/template/template.yaml ++ rules: ++ - allow: [Template] ++ ++ # Local example organizational data ++ - type: file ++ target: ./examples/org.yaml ++ rules: ++ - allow: [User, Group] +diff --git a/app-config.yaml b/app-config.yaml +index 4a058de..ca52ec5 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 +@@ -41,3 +38,3 @@ integrations: + # 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 ++ # about setting up the GitHub integration here: https://backstage.io/docs/integrations/github/locations#configuration + token: ${GITHUB_TOKEN} +@@ -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 + +@@ -66,3 +66,5 @@ auth: + # see https://backstage.io/docs/auth/ to learn about auth providers +- providers: {} ++ providers: ++ # See https://backstage.io/docs/auth/guest/provider ++ guest: {} + +@@ -103 +105,9 @@ catalog: + # - allow: [User, Group] ++ ++kubernetes: ++ # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options ++ ++# see https://backstage.io/docs/permissions/getting-started for more on the permission framework ++permission: ++ # setting this to `false` will disable permissions ++ enabled: true +diff --git a/backstage.json b/backstage.json +index 6076f7d..230d39d 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "1.7.0" ++ "version": "1.30.0" + } +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..80a963e 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,13 +33,15 @@ + "devDependencies": { +- "@backstage/cli": "^0.20.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/e2e-test-utils": "^0.1.1", ++ "@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": "^10.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.6.4", +- "node-gyp": "^9.0.0" ++ "typescript": "~5.4.0" + }, + "resolutions": { +- "@types/react": "^17", +- "@types/react-dom": "^17" ++ "@types/react": "^18", ++ "@types/react-dom": "^18" + }, +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..839ff88 +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,27 @@ ++/* ++ * 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('/'); ++ ++ const enterButton = page.getByRole('button', { name: 'Enter' }); ++ await expect(enterButton).toBeVisible(); ++ await enterButton.click(); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index dd1b831..346ffc1 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -13,34 +13,29 @@ + "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.5.10", ++ "@backstage/catalog-model": "^1.6.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/core-app-api": "^1.14.2", ++ "@backstage/core-components": "^0.14.10", ++ "@backstage/core-plugin-api": "^1.9.3", ++ "@backstage/integration-react": "^1.1.30", ++ "@backstage/plugin-api-docs": "^0.11.8", ++ "@backstage/plugin-catalog": "^1.22.0", ++ "@backstage/plugin-catalog-common": "^1.0.26", ++ "@backstage/plugin-catalog-graph": "^0.4.8", ++ "@backstage/plugin-catalog-import": "^0.12.2", ++ "@backstage/plugin-catalog-react": "^1.12.3", ++ "@backstage/plugin-kubernetes": "^0.11.13", ++ "@backstage/plugin-org": "^0.6.28", ++ "@backstage/plugin-permission-react": "^0.4.25", ++ "@backstage/plugin-scaffolder": "^1.24.0", ++ "@backstage/plugin-search": "^1.4.15", ++ "@backstage/plugin-search-react": "^1.7.14", ++ "@backstage/plugin-techdocs": "^1.10.8", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.13", ++ "@backstage/plugin-techdocs-react": "^1.2.7", ++ "@backstage/plugin-user-settings": "^0.8.11", ++ "@backstage/theme": "^0.5.6", + "@material-ui/core": "^4.12.2", +@@ -48,4 +43,4 @@ + "history": "^5.0.0", +- "react": "^17.0.2", +- "react-dom": "^17.0.2", ++ "react": "^18.0.2", ++ "react-dom": "^18.0.2", + "react-router": "^6.3.0", +@@ -55,12 +50,10 @@ + "devDependencies": { +- "@backstage/test-utils": "^1.2.1", +- "@testing-library/jest-dom": "^5.10.1", +- "@testing-library/react": "^12.1.3", ++ "@backstage/test-utils": "^1.5.10", ++ "@playwright/test": "^1.32.3", ++ "@testing-library/jest-dom": "^6.0.0", ++ "@testing-library/react": "^14.0.0", + "@testing-library/user-event": "^14.0.0", +- "@types/node": "^16.11.26", ++ "@testing-library/dom": "^9.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..18da7c4 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -8,5 +8,4 @@ + name="description" +- content="Backstage is an open platform for building developer portals" ++ content="Backstage is an open source framework for building developer portals" + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index b94cac7..ec8ba1d 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { renderWithEffects } from '@backstage/test-utils'; ++import { render, waitFor } from '@testing-library/react'; + import App from './App'; +@@ -22,4 +22,7 @@ describe('App', () => { + +- const rendered = await renderWithEffects(); +- expect(rendered.baseElement).toBeInTheDocument(); ++ const rendered = render(); ++ ++ await waitFor(() => { ++ expect(rendered.baseElement).toBeInTheDocument(); ++ }); + }); +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 46cb786..e24c468 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'; +@@ -15,3 +15,2 @@ import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +-import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +@@ -29,5 +28,9 @@ import { Root } from './components/Root'; + +-import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; ++import { ++ AlertDisplay, ++ OAuthRequestDialog, ++ SignInPage, ++} 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 +45,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -48,2 +52,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -53,7 +58,7 @@ const app = createApp({ + }, ++ components: { ++ SignInPage: props => , ++ }, + }); + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( +@@ -79,6 +84,2 @@ const routes = ( + } /> +- } +- /> + ( +- ++export default app.createRoot( ++ <> + +@@ -106,5 +107,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..853d175 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,6 +1,5 @@ + 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'; + import ExtensionIcon from '@material-ui/icons/Extension'; +-import MapIcon from '@material-ui/icons/MyLocation'; + import LibraryBooks from '@material-ui/icons/LibraryBooks'; +@@ -9,3 +8,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -25,2 +23,3 @@ import { + useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -28,2 +27,4 @@ import MenuIcon from '@material-ui/icons/Menu'; + import SearchIcon from '@material-ui/icons/Search'; ++import { MyGroupsSidebarItem } from '@backstage/plugin-org'; ++import GroupIcon from '@material-ui/icons/People'; + +@@ -50,9 +51,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -74,2 +69,7 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + ++ + +@@ -80,3 +80,3 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ {/* Items in this group will be scrollable if they run out of space */} + +diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx +index 54a05ee..6164c76 100644 +--- a/packages/app/src/components/catalog/EntityPage.tsx ++++ b/packages/app/src/components/catalog/EntityPage.tsx +@@ -27,7 +27,5 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +-import { +- isGithubActionsAvailable, +- EntityGithubActionsContent, +-} from '@backstage/plugin-github-actions'; + import { +@@ -58,2 +56,7 @@ import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; + ++import { ++ EntityKubernetesContent, ++ isKubernetesAvailable, ++} from '@backstage/plugin-kubernetes'; ++ + const techdocsContent = ( +@@ -70,5 +73,9 @@ const cicdContent = ( + +- +- +- ++ {/* ++ Here you can add support for different CI/CD services, for example ++ using @backstage-community/plugin-github-actions as follows: ++ ++ ++ ++ */} + +@@ -103,2 +110,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -142,2 +157,10 @@ const serviceEntityPage = ( + ++ ++ ++ ++ + +@@ -180,2 +203,10 @@ const websiteEntityPage = ( + ++ ++ ++ ++ + +@@ -292,5 +323,8 @@ const groupPage = ( + +- ++ + + ++ ++ ++ + +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/app/src/index.tsx b/packages/app/src/index.tsx +index b16aaf7..d875c77 100644 +--- a/packages/app/src/index.tsx ++++ b/packages/app/src/index.tsx +@@ -2,5 +2,5 @@ import '@backstage/cli/asset-types'; + import React from 'react'; +-import ReactDOM from 'react-dom'; ++import ReactDOM from 'react-dom/client'; + import App from './App'; + +-ReactDOM.render(, document.getElementById('root')); ++ReactDOM.createRoot(document.getElementById('root')!).render(); +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index f0be3ac..bef67b5 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 + +@@ -41,2 +47,5 @@ RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid + ++# This will include the examples, if you don't need these simply remove this line ++COPY --chown=node:node examples ./examples ++ + # Then copy the rest of the backend bundle, along with any other files we might want. +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..6a14abc 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -18,25 +18,31 @@ + "dependencies": { ++ "@backstage/backend-common": "^0.24.0", ++ "@backstage/backend-defaults": "^0.4.2", ++ "@backstage/backend-tasks": "^0.6.0", ++ "@backstage/config": "^1.2.0", ++ "@backstage/plugin-app-backend": "^0.3.72", ++ "@backstage/plugin-auth-backend": "^0.22.10", ++ "@backstage/plugin-auth-backend-module-github-provider": "^0.1.20", ++ "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.9", ++ "@backstage/plugin-auth-node": "^0.5.0", ++ "@backstage/plugin-catalog-backend": "^1.25.0", ++ "@backstage/plugin-catalog-backend-module-logs": "^0.0.2", ++ "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.21", ++ "@backstage/plugin-kubernetes-backend": "^0.18.4", ++ "@backstage/plugin-permission-backend": "^0.5.47", ++ "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.1.20", ++ "@backstage/plugin-permission-common": "^0.8.1", ++ "@backstage/plugin-permission-node": "^0.8.1", ++ "@backstage/plugin-proxy-backend": "^0.5.4", ++ "@backstage/plugin-scaffolder-backend": "^1.24.0", ++ "@backstage/plugin-search-backend": "^1.5.15", ++ "@backstage/plugin-search-backend-module-catalog": "^0.2.0", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.33", ++ "@backstage/plugin-search-backend-module-techdocs": "^0.2.0", ++ "@backstage/plugin-search-backend-node": "^1.3.0", ++ "@backstage/plugin-techdocs-backend": "^1.10.10", + "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", +- "dockerode": "^3.3.1", +- "express": "^4.17.1", +- "express-promise-router": "^4.1.0", +- "pg": "^8.3.0", ++ "better-sqlite3": "^9.0.0", ++ "node-gyp": "^10.0.0", ++ "pg": "^8.11.3", + "winston": "^3.2.1" +@@ -44,6 +50,5 @@ + "devDependencies": { +- "@backstage/cli": "^0.20.0", +- "@types/dockerode": "^3.3.0", +- "@types/express-serve-static-core": "^4.17.5", ++ "@backstage/cli": "^0.27.0", + "@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.test.ts b/packages/backend/src/index.test.ts +deleted file mode 100644 +index 7814b8c..0000000 +--- a/packages/backend/src/index.test.ts ++++ /dev/null +@@ -1,8 +0,0 @@ +-import { PluginEnvironment } from './types'; +- +-describe('test', () => { +- it('unbreaks the test runner', () => { +- const unbreaker = {} as PluginEnvironment; +- expect(unbreaker).toBeTruthy(); +- }); +-}); +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index c4736a5..f57d4c5 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -8,108 +8,47 @@ + +-import Router from 'express-promise-router'; +-import { +- createServiceBuilder, +- loadBackendConfig, +- getRootLogger, +- useHotMemoize, +- notFoundHandler, +- CacheManager, +- DatabaseManager, +- SingleHostDiscovery, +- UrlReaders, +- ServerTokenManager, +-} from '@backstage/backend-common'; +-import { TaskScheduler } from '@backstage/backend-tasks'; +-import { Config } from '@backstage/config'; +-import app from './plugins/app'; +-import auth from './plugins/auth'; +-import catalog from './plugins/catalog'; +-import scaffolder from './plugins/scaffolder'; +-import proxy from './plugins/proxy'; +-import techdocs from './plugins/techdocs'; +-import search from './plugins/search'; +-import { PluginEnvironment } from './types'; +-import { ServerPermissionClient } from '@backstage/plugin-permission-node'; +-import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; ++import { createBackend } from '@backstage/backend-defaults'; + +-function makeCreateEnv(config: Config) { +- const root = getRootLogger(); +- const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); +- const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); ++const backend = createBackend(); + +- const identity = DefaultIdentityClient.create({ +- discovery, +- }); +- const permissions = ServerPermissionClient.fromConfig(config, { +- discovery, +- tokenManager, +- }); ++backend.add(import('@backstage/plugin-app-backend/alpha')); ++backend.add(import('@backstage/plugin-proxy-backend/alpha')); ++backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); ++backend.add(import('@backstage/plugin-techdocs-backend/alpha')); + +- root.info(`Created UrlReader ${reader}`); ++// auth plugin ++backend.add(import('@backstage/plugin-auth-backend')); ++// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin ++backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); ++// See https://backstage.io/docs/auth/guest/provider + +- return (plugin: string): PluginEnvironment => { +- const logger = root.child({ type: 'plugin', plugin }); +- const database = databaseManager.forPlugin(plugin); +- const cache = cacheManager.forPlugin(plugin); +- const scheduler = taskScheduler.forPlugin(plugin); +- return { +- logger, +- database, +- cache, +- config, +- reader, +- discovery, +- tokenManager, +- scheduler, +- permissions, +- identity, +- }; +- }; +-} ++// catalog plugin ++backend.add(import('@backstage/plugin-catalog-backend/alpha')); ++backend.add( ++ import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ++); + +-async function main() { +- const config = await loadBackendConfig({ +- argv: process.argv, +- logger: getRootLogger(), +- }); +- const createEnv = makeCreateEnv(config); ++// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors ++backend.add(import('@backstage/plugin-catalog-backend-module-logs')); + +- const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); +- const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); +- const authEnv = useHotMemoize(module, () => createEnv('auth')); +- const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); +- const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); +- const searchEnv = useHotMemoize(module, () => createEnv('search')); +- const appEnv = useHotMemoize(module, () => createEnv('app')); ++// permission plugin ++backend.add(import('@backstage/plugin-permission-backend/alpha')); ++// See https://backstage.io/docs/permissions/getting-started for how to create your own permission policy ++backend.add( ++ import('@backstage/plugin-permission-backend-module-allow-all-policy'), ++); + +- const apiRouter = Router(); +- apiRouter.use('/catalog', await catalog(catalogEnv)); +- apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); +- apiRouter.use('/auth', await auth(authEnv)); +- apiRouter.use('/techdocs', await techdocs(techdocsEnv)); +- apiRouter.use('/proxy', await proxy(proxyEnv)); +- apiRouter.use('/search', await search(searchEnv)); ++// search plugin ++backend.add(import('@backstage/plugin-search-backend/alpha')); + +- // Add backends ABOVE this line; this 404 handler is the catch-all fallback +- apiRouter.use(notFoundHandler()); ++// search engine ++// See https://backstage.io/docs/features/search/search-engines ++backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); + +- const service = createServiceBuilder(module) +- .loadConfig(config) +- .addRouter('/api', apiRouter) +- .addRouter('', await app(appEnv)); ++// search collators ++backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); ++backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); + +- await service.start().catch(err => { +- console.log(err); +- process.exit(1); +- }); +-} ++// kubernetes ++backend.add(import('@backstage/plugin-kubernetes-backend/alpha')); + +-module.hot?.accept(); +-main().catch(error => { +- console.error('Backend failed to start up', error); +- process.exit(1); +-}); ++backend.start(); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +deleted file mode 100644 +index 7c37f68..0000000 +--- a/packages/backend/src/plugins/app.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { createRouter } from '@backstage/plugin-app-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- return await createRouter({ +- 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 +deleted file mode 100644 +index 77eb6aa..0000000 +--- a/packages/backend/src/plugins/auth.ts ++++ /dev/null +@@ -1,54 +0,0 @@ +-import { +- createRouter, +- providers, +- defaultAuthProviderFactories, +-} from '@backstage/plugin-auth-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-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 +deleted file mode 100644 +index 876cb6b..0000000 +--- a/packages/backend/src/plugins/catalog.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +-import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const builder = await CatalogBuilder.create(env); +- builder.addProcessor(new ScaffolderEntitiesProcessor()); +- const { processingEngine, router } = await builder.build(); +- await processingEngine.start(); +- return router; +-} +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +deleted file mode 100644 +index 54ec393..0000000 +--- a/packages/backend/src/plugins/proxy.ts ++++ /dev/null +@@ -1,13 +0,0 @@ +-import { createRouter } from '@backstage/plugin-proxy-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-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 +deleted file mode 100644 +index ef46f07..0000000 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ /dev/null +@@ -1,21 +0,0 @@ +-import { CatalogClient } from '@backstage/catalog-client'; +-import { createRouter } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import type { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const catalogClient = new CatalogClient({ +- discoveryApi: env.discovery, +- }); +- +- return await createRouter({ +- logger: env.logger, +- config: env.config, +- database: env.database, +- reader: env.reader, +- catalogClient, +- identity: env.identity, +- }); +-} +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +deleted file mode 100644 +index e9469dc..0000000 +--- a/packages/backend/src/plugins/search.ts ++++ /dev/null +@@ -1,66 +0,0 @@ +-import { useHotCleanup } from '@backstage/backend-common'; +-import { createRouter } from '@backstage/plugin-search-backend'; +-import { +- IndexBuilder, +- LunrSearchEngine, +-} from '@backstage/plugin-search-backend-node'; +-import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; +-import { Router } from 'express'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- // Initialize a connection to a search engine. +- 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 +- // 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({ +- schedule, +- factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { +- discovery: env.discovery, +- logger: env.logger, +- tokenManager: env.tokenManager, +- }), +- }); +- +- // The scheduler controls when documents are gathered from collators and sent +- // to the search engine for indexing. +- const { scheduler } = await indexBuilder.build(); +- scheduler.start(); +- +- useHotCleanup(module, () => scheduler.stop()); +- +- return await createRouter({ +- engine: indexBuilder.getSearchEngine(), +- 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 +deleted file mode 100644 +index be8bb0c..0000000 +--- a/packages/backend/src/plugins/techdocs.ts ++++ /dev/null +@@ -1,51 +0,0 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; +-import { +- createRouter, +- Generators, +- Preparers, +- Publisher, +-} from '@backstage/plugin-techdocs-backend'; +-import Docker from 'dockerode'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(env.config, { +- logger: env.logger, +- reader: env.reader, +- }); +- +- // Docker client (conditionally) used by the generators, based on techdocs.generators config. +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- +- // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(env.config, { +- logger: env.logger, +- containerRunner, +- }); +- +- // Publisher is used for +- // 1. Publishing generated files to storage +- // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(env.config, { +- logger: env.logger, +- discovery: env.discovery, +- }); +- +- // checks if the publisher is working and logs the result +- await publisher.getReadiness(); +- +- return await createRouter({ +- preparers, +- generators, +- publisher, +- 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 +deleted file mode 100644 +index 9cd2c74..0000000 +--- a/packages/backend/src/types.ts ++++ /dev/null +@@ -1,25 +0,0 @@ +-import { Logger } from 'winston'; +-import { Config } from '@backstage/config'; +-import { +- PluginCacheManager, +- PluginDatabaseManager, +- 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'; +- +-export type PluginEnvironment = { +- logger: Logger; +- database: PluginDatabaseManager; +- cache: PluginCacheManager; +- config: Config; +- reader: UrlReader; +- 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..733be13 +--- /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 dev', ++ 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 +index 5ad7fe2..c37fc61 100644 +--- a/yarn.lock ++++ b/yarn.lock +@@ -1 +1,3 @@ +-# intentionally left empty ++# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. ++# yarn lockfile v1 ++ diff --git a/diffs/0.4.33..0.5.18.diff b/diffs/0.4.33..0.5.18.diff new file mode 100644 index 00000000..6358a2ad --- /dev/null +++ b/diffs/0.4.33..0.5.18.diff @@ -0,0 +1,1389 @@ +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..5d426f5 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -11,12 +11,7 @@ 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' + +- # config options: https://node-postgres.com/api/client ++ # config options: https://node-postgres.com/apis/client + database: +@@ -35,6 +30,26 @@ backend: + ++auth: ++ providers: ++ guest: {} ++ + 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. +- locations: [] ++ locations: ++ # Local example data, replace this with your production config, these are intended for demo use only. ++ # File locations are relative to the backend process, typically in a deployed context, such as in a Docker container, this will be the root ++ - type: file ++ target: ./examples/entities.yaml ++ ++ # Local example template ++ - type: file ++ target: ./examples/template/template.yaml ++ rules: ++ - allow: [Template] ++ ++ # Local example organizational data ++ - type: file ++ target: ./examples/org.yaml ++ rules: ++ - allow: [User, Group] +diff --git a/app-config.yaml b/app-config.yaml +index 4a058de..ca52ec5 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 +@@ -41,3 +38,3 @@ integrations: + # 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 ++ # about setting up the GitHub integration here: https://backstage.io/docs/integrations/github/locations#configuration + token: ${GITHUB_TOKEN} +@@ -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 + +@@ -66,3 +66,5 @@ auth: + # see https://backstage.io/docs/auth/ to learn about auth providers +- providers: {} ++ providers: ++ # See https://backstage.io/docs/auth/guest/provider ++ guest: {} + +@@ -103 +105,9 @@ catalog: + # - allow: [User, Group] ++ ++kubernetes: ++ # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options ++ ++# see https://backstage.io/docs/permissions/getting-started for more on the permission framework ++permission: ++ # setting this to `false` will disable permissions ++ enabled: true +diff --git a/backstage.json b/backstage.json +index 96bed22..230d39d 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "1.7.1" ++ "version": "1.30.0" + } +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..80a963e 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,13 +33,15 @@ + "devDependencies": { +- "@backstage/cli": "^0.20.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/e2e-test-utils": "^0.1.1", ++ "@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": "^10.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.6.4", +- "node-gyp": "^9.0.0" ++ "typescript": "~5.4.0" + }, + "resolutions": { +- "@types/react": "^17", +- "@types/react-dom": "^17" ++ "@types/react": "^18", ++ "@types/react-dom": "^18" + }, +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..839ff88 +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,27 @@ ++/* ++ * 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('/'); ++ ++ const enterButton = page.getByRole('button', { name: 'Enter' }); ++ await expect(enterButton).toBeVisible(); ++ await enterButton.click(); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index dd1b831..346ffc1 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -13,34 +13,29 @@ + "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.5.10", ++ "@backstage/catalog-model": "^1.6.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/core-app-api": "^1.14.2", ++ "@backstage/core-components": "^0.14.10", ++ "@backstage/core-plugin-api": "^1.9.3", ++ "@backstage/integration-react": "^1.1.30", ++ "@backstage/plugin-api-docs": "^0.11.8", ++ "@backstage/plugin-catalog": "^1.22.0", ++ "@backstage/plugin-catalog-common": "^1.0.26", ++ "@backstage/plugin-catalog-graph": "^0.4.8", ++ "@backstage/plugin-catalog-import": "^0.12.2", ++ "@backstage/plugin-catalog-react": "^1.12.3", ++ "@backstage/plugin-kubernetes": "^0.11.13", ++ "@backstage/plugin-org": "^0.6.28", ++ "@backstage/plugin-permission-react": "^0.4.25", ++ "@backstage/plugin-scaffolder": "^1.24.0", ++ "@backstage/plugin-search": "^1.4.15", ++ "@backstage/plugin-search-react": "^1.7.14", ++ "@backstage/plugin-techdocs": "^1.10.8", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.13", ++ "@backstage/plugin-techdocs-react": "^1.2.7", ++ "@backstage/plugin-user-settings": "^0.8.11", ++ "@backstage/theme": "^0.5.6", + "@material-ui/core": "^4.12.2", +@@ -48,4 +43,4 @@ + "history": "^5.0.0", +- "react": "^17.0.2", +- "react-dom": "^17.0.2", ++ "react": "^18.0.2", ++ "react-dom": "^18.0.2", + "react-router": "^6.3.0", +@@ -55,12 +50,10 @@ + "devDependencies": { +- "@backstage/test-utils": "^1.2.1", +- "@testing-library/jest-dom": "^5.10.1", +- "@testing-library/react": "^12.1.3", ++ "@backstage/test-utils": "^1.5.10", ++ "@playwright/test": "^1.32.3", ++ "@testing-library/jest-dom": "^6.0.0", ++ "@testing-library/react": "^14.0.0", + "@testing-library/user-event": "^14.0.0", +- "@types/node": "^16.11.26", ++ "@testing-library/dom": "^9.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..18da7c4 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -8,5 +8,4 @@ + name="description" +- content="Backstage is an open platform for building developer portals" ++ content="Backstage is an open source framework for building developer portals" + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index b94cac7..ec8ba1d 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { renderWithEffects } from '@backstage/test-utils'; ++import { render, waitFor } from '@testing-library/react'; + import App from './App'; +@@ -22,4 +22,7 @@ describe('App', () => { + +- const rendered = await renderWithEffects(); +- expect(rendered.baseElement).toBeInTheDocument(); ++ const rendered = render(); ++ ++ await waitFor(() => { ++ expect(rendered.baseElement).toBeInTheDocument(); ++ }); + }); +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 46cb786..e24c468 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'; +@@ -15,3 +15,2 @@ import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +-import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +@@ -29,5 +28,9 @@ import { Root } from './components/Root'; + +-import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; ++import { ++ AlertDisplay, ++ OAuthRequestDialog, ++ SignInPage, ++} 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 +45,3 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); +@@ -48,2 +52,3 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); +@@ -53,7 +58,7 @@ const app = createApp({ + }, ++ components: { ++ SignInPage: props => , ++ }, + }); + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( +@@ -79,6 +84,2 @@ const routes = ( + } /> +- } +- /> + ( +- ++export default app.createRoot( ++ <> + +@@ -106,5 +107,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..853d175 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,6 +1,5 @@ + 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'; + import ExtensionIcon from '@material-ui/icons/Extension'; +-import MapIcon from '@material-ui/icons/MyLocation'; + import LibraryBooks from '@material-ui/icons/LibraryBooks'; +@@ -9,3 +8,2 @@ import LogoFull from './LogoFull'; + import LogoIcon from './LogoIcon'; +-import { NavLink } from 'react-router-dom'; + import { +@@ -25,2 +23,3 @@ import { + useSidebarOpenState, ++ Link, + } from '@backstage/core-components'; +@@ -28,2 +27,4 @@ import MenuIcon from '@material-ui/icons/Menu'; + import SearchIcon from '@material-ui/icons/Search'; ++import { MyGroupsSidebarItem } from '@backstage/plugin-org'; ++import GroupIcon from '@material-ui/icons/People'; + +@@ -50,9 +51,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -74,2 +69,7 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + ++ + +@@ -80,3 +80,3 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ {/* Items in this group will be scrollable if they run out of space */} + +diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx +index 54a05ee..6164c76 100644 +--- a/packages/app/src/components/catalog/EntityPage.tsx ++++ b/packages/app/src/components/catalog/EntityPage.tsx +@@ -27,7 +27,5 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +-import { +- isGithubActionsAvailable, +- EntityGithubActionsContent, +-} from '@backstage/plugin-github-actions'; + import { +@@ -58,2 +56,7 @@ import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; + ++import { ++ EntityKubernetesContent, ++ isKubernetesAvailable, ++} from '@backstage/plugin-kubernetes'; ++ + const techdocsContent = ( +@@ -70,5 +73,9 @@ const cicdContent = ( + +- +- +- ++ {/* ++ Here you can add support for different CI/CD services, for example ++ using @backstage-community/plugin-github-actions as follows: ++ ++ ++ ++ */} + +@@ -103,2 +110,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -142,2 +157,10 @@ const serviceEntityPage = ( + ++ ++ ++ ++ + +@@ -180,2 +203,10 @@ const websiteEntityPage = ( + ++ ++ ++ ++ + +@@ -292,5 +323,8 @@ const groupPage = ( + +- ++ + + ++ ++ ++ + +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/app/src/index.tsx b/packages/app/src/index.tsx +index b16aaf7..d875c77 100644 +--- a/packages/app/src/index.tsx ++++ b/packages/app/src/index.tsx +@@ -2,5 +2,5 @@ import '@backstage/cli/asset-types'; + import React from 'react'; +-import ReactDOM from 'react-dom'; ++import ReactDOM from 'react-dom/client'; + import App from './App'; + +-ReactDOM.render(, document.getElementById('root')); ++ReactDOM.createRoot(document.getElementById('root')!).render(); +diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile +index f0be3ac..bef67b5 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 + +@@ -41,2 +47,5 @@ RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid + ++# This will include the examples, if you don't need these simply remove this line ++COPY --chown=node:node examples ./examples ++ + # Then copy the rest of the backend bundle, along with any other files we might want. +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..6a14abc 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -18,25 +18,31 @@ + "dependencies": { ++ "@backstage/backend-common": "^0.24.0", ++ "@backstage/backend-defaults": "^0.4.2", ++ "@backstage/backend-tasks": "^0.6.0", ++ "@backstage/config": "^1.2.0", ++ "@backstage/plugin-app-backend": "^0.3.72", ++ "@backstage/plugin-auth-backend": "^0.22.10", ++ "@backstage/plugin-auth-backend-module-github-provider": "^0.1.20", ++ "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.9", ++ "@backstage/plugin-auth-node": "^0.5.0", ++ "@backstage/plugin-catalog-backend": "^1.25.0", ++ "@backstage/plugin-catalog-backend-module-logs": "^0.0.2", ++ "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.21", ++ "@backstage/plugin-kubernetes-backend": "^0.18.4", ++ "@backstage/plugin-permission-backend": "^0.5.47", ++ "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.1.20", ++ "@backstage/plugin-permission-common": "^0.8.1", ++ "@backstage/plugin-permission-node": "^0.8.1", ++ "@backstage/plugin-proxy-backend": "^0.5.4", ++ "@backstage/plugin-scaffolder-backend": "^1.24.0", ++ "@backstage/plugin-search-backend": "^1.5.15", ++ "@backstage/plugin-search-backend-module-catalog": "^0.2.0", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.33", ++ "@backstage/plugin-search-backend-module-techdocs": "^0.2.0", ++ "@backstage/plugin-search-backend-node": "^1.3.0", ++ "@backstage/plugin-techdocs-backend": "^1.10.10", + "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", +- "dockerode": "^3.3.1", +- "express": "^4.17.1", +- "express-promise-router": "^4.1.0", +- "pg": "^8.3.0", ++ "better-sqlite3": "^9.0.0", ++ "node-gyp": "^10.0.0", ++ "pg": "^8.11.3", + "winston": "^3.2.1" +@@ -44,6 +50,5 @@ + "devDependencies": { +- "@backstage/cli": "^0.20.0", +- "@types/dockerode": "^3.3.0", +- "@types/express-serve-static-core": "^4.17.5", ++ "@backstage/cli": "^0.27.0", + "@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.test.ts b/packages/backend/src/index.test.ts +deleted file mode 100644 +index 7814b8c..0000000 +--- a/packages/backend/src/index.test.ts ++++ /dev/null +@@ -1,8 +0,0 @@ +-import { PluginEnvironment } from './types'; +- +-describe('test', () => { +- it('unbreaks the test runner', () => { +- const unbreaker = {} as PluginEnvironment; +- expect(unbreaker).toBeTruthy(); +- }); +-}); +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index c4736a5..f57d4c5 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -8,108 +8,47 @@ + +-import Router from 'express-promise-router'; +-import { +- createServiceBuilder, +- loadBackendConfig, +- getRootLogger, +- useHotMemoize, +- notFoundHandler, +- CacheManager, +- DatabaseManager, +- SingleHostDiscovery, +- UrlReaders, +- ServerTokenManager, +-} from '@backstage/backend-common'; +-import { TaskScheduler } from '@backstage/backend-tasks'; +-import { Config } from '@backstage/config'; +-import app from './plugins/app'; +-import auth from './plugins/auth'; +-import catalog from './plugins/catalog'; +-import scaffolder from './plugins/scaffolder'; +-import proxy from './plugins/proxy'; +-import techdocs from './plugins/techdocs'; +-import search from './plugins/search'; +-import { PluginEnvironment } from './types'; +-import { ServerPermissionClient } from '@backstage/plugin-permission-node'; +-import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; ++import { createBackend } from '@backstage/backend-defaults'; + +-function makeCreateEnv(config: Config) { +- const root = getRootLogger(); +- const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); +- const tokenManager = ServerTokenManager.noop(); +- const taskScheduler = TaskScheduler.fromConfig(config); ++const backend = createBackend(); + +- const identity = DefaultIdentityClient.create({ +- discovery, +- }); +- const permissions = ServerPermissionClient.fromConfig(config, { +- discovery, +- tokenManager, +- }); ++backend.add(import('@backstage/plugin-app-backend/alpha')); ++backend.add(import('@backstage/plugin-proxy-backend/alpha')); ++backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); ++backend.add(import('@backstage/plugin-techdocs-backend/alpha')); + +- root.info(`Created UrlReader ${reader}`); ++// auth plugin ++backend.add(import('@backstage/plugin-auth-backend')); ++// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin ++backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); ++// See https://backstage.io/docs/auth/guest/provider + +- return (plugin: string): PluginEnvironment => { +- const logger = root.child({ type: 'plugin', plugin }); +- const database = databaseManager.forPlugin(plugin); +- const cache = cacheManager.forPlugin(plugin); +- const scheduler = taskScheduler.forPlugin(plugin); +- return { +- logger, +- database, +- cache, +- config, +- reader, +- discovery, +- tokenManager, +- scheduler, +- permissions, +- identity, +- }; +- }; +-} ++// catalog plugin ++backend.add(import('@backstage/plugin-catalog-backend/alpha')); ++backend.add( ++ import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ++); + +-async function main() { +- const config = await loadBackendConfig({ +- argv: process.argv, +- logger: getRootLogger(), +- }); +- const createEnv = makeCreateEnv(config); ++// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors ++backend.add(import('@backstage/plugin-catalog-backend-module-logs')); + +- const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); +- const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); +- const authEnv = useHotMemoize(module, () => createEnv('auth')); +- const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); +- const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); +- const searchEnv = useHotMemoize(module, () => createEnv('search')); +- const appEnv = useHotMemoize(module, () => createEnv('app')); ++// permission plugin ++backend.add(import('@backstage/plugin-permission-backend/alpha')); ++// See https://backstage.io/docs/permissions/getting-started for how to create your own permission policy ++backend.add( ++ import('@backstage/plugin-permission-backend-module-allow-all-policy'), ++); + +- const apiRouter = Router(); +- apiRouter.use('/catalog', await catalog(catalogEnv)); +- apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); +- apiRouter.use('/auth', await auth(authEnv)); +- apiRouter.use('/techdocs', await techdocs(techdocsEnv)); +- apiRouter.use('/proxy', await proxy(proxyEnv)); +- apiRouter.use('/search', await search(searchEnv)); ++// search plugin ++backend.add(import('@backstage/plugin-search-backend/alpha')); + +- // Add backends ABOVE this line; this 404 handler is the catch-all fallback +- apiRouter.use(notFoundHandler()); ++// search engine ++// See https://backstage.io/docs/features/search/search-engines ++backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); + +- const service = createServiceBuilder(module) +- .loadConfig(config) +- .addRouter('/api', apiRouter) +- .addRouter('', await app(appEnv)); ++// search collators ++backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); ++backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); + +- await service.start().catch(err => { +- console.log(err); +- process.exit(1); +- }); +-} ++// kubernetes ++backend.add(import('@backstage/plugin-kubernetes-backend/alpha')); + +-module.hot?.accept(); +-main().catch(error => { +- console.error('Backend failed to start up', error); +- process.exit(1); +-}); ++backend.start(); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +deleted file mode 100644 +index 7c37f68..0000000 +--- a/packages/backend/src/plugins/app.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { createRouter } from '@backstage/plugin-app-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- return await createRouter({ +- 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 +deleted file mode 100644 +index 77eb6aa..0000000 +--- a/packages/backend/src/plugins/auth.ts ++++ /dev/null +@@ -1,54 +0,0 @@ +-import { +- createRouter, +- providers, +- defaultAuthProviderFactories, +-} from '@backstage/plugin-auth-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-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 +deleted file mode 100644 +index 876cb6b..0000000 +--- a/packages/backend/src/plugins/catalog.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +-import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const builder = await CatalogBuilder.create(env); +- builder.addProcessor(new ScaffolderEntitiesProcessor()); +- const { processingEngine, router } = await builder.build(); +- await processingEngine.start(); +- return router; +-} +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +deleted file mode 100644 +index 54ec393..0000000 +--- a/packages/backend/src/plugins/proxy.ts ++++ /dev/null +@@ -1,13 +0,0 @@ +-import { createRouter } from '@backstage/plugin-proxy-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-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 +deleted file mode 100644 +index ef46f07..0000000 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ /dev/null +@@ -1,21 +0,0 @@ +-import { CatalogClient } from '@backstage/catalog-client'; +-import { createRouter } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import type { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const catalogClient = new CatalogClient({ +- discoveryApi: env.discovery, +- }); +- +- return await createRouter({ +- logger: env.logger, +- config: env.config, +- database: env.database, +- reader: env.reader, +- catalogClient, +- identity: env.identity, +- }); +-} +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +deleted file mode 100644 +index e9469dc..0000000 +--- a/packages/backend/src/plugins/search.ts ++++ /dev/null +@@ -1,66 +0,0 @@ +-import { useHotCleanup } from '@backstage/backend-common'; +-import { createRouter } from '@backstage/plugin-search-backend'; +-import { +- IndexBuilder, +- LunrSearchEngine, +-} from '@backstage/plugin-search-backend-node'; +-import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend'; +-import { Router } from 'express'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- // Initialize a connection to a search engine. +- 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 +- // 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({ +- schedule, +- factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, { +- discovery: env.discovery, +- logger: env.logger, +- tokenManager: env.tokenManager, +- }), +- }); +- +- // The scheduler controls when documents are gathered from collators and sent +- // to the search engine for indexing. +- const { scheduler } = await indexBuilder.build(); +- scheduler.start(); +- +- useHotCleanup(module, () => scheduler.stop()); +- +- return await createRouter({ +- engine: indexBuilder.getSearchEngine(), +- 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 +deleted file mode 100644 +index be8bb0c..0000000 +--- a/packages/backend/src/plugins/techdocs.ts ++++ /dev/null +@@ -1,51 +0,0 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; +-import { +- createRouter, +- Generators, +- Preparers, +- Publisher, +-} from '@backstage/plugin-techdocs-backend'; +-import Docker from 'dockerode'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(env.config, { +- logger: env.logger, +- reader: env.reader, +- }); +- +- // Docker client (conditionally) used by the generators, based on techdocs.generators config. +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- +- // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(env.config, { +- logger: env.logger, +- containerRunner, +- }); +- +- // Publisher is used for +- // 1. Publishing generated files to storage +- // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(env.config, { +- logger: env.logger, +- discovery: env.discovery, +- }); +- +- // checks if the publisher is working and logs the result +- await publisher.getReadiness(); +- +- return await createRouter({ +- preparers, +- generators, +- publisher, +- 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 +deleted file mode 100644 +index 9cd2c74..0000000 +--- a/packages/backend/src/types.ts ++++ /dev/null +@@ -1,25 +0,0 @@ +-import { Logger } from 'winston'; +-import { Config } from '@backstage/config'; +-import { +- PluginCacheManager, +- PluginDatabaseManager, +- 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'; +- +-export type PluginEnvironment = { +- logger: Logger; +- database: PluginDatabaseManager; +- cache: PluginCacheManager; +- config: Config; +- reader: UrlReader; +- 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..733be13 +--- /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 dev', ++ 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 +index 5ad7fe2..c37fc61 100644 +--- a/yarn.lock ++++ b/yarn.lock +@@ -1 +1,3 @@ +-# intentionally left empty ++# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. ++# yarn lockfile v1 ++ diff --git a/diffs/0.4.34..0.5.18.diff b/diffs/0.4.34..0.5.18.diff new file mode 100644 index 00000000..bdd3e0d8 --- /dev/null +++ b/diffs/0.4.34..0.5.18.diff @@ -0,0 +1,1290 @@ +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..5d426f5 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -15,3 +15,3 @@ backend: + +- # config options: https://node-postgres.com/api/client ++ # config options: https://node-postgres.com/apis/client + database: +@@ -30,6 +30,26 @@ backend: + ++auth: ++ providers: ++ guest: {} ++ + 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. +- locations: [] ++ locations: ++ # Local example data, replace this with your production config, these are intended for demo use only. ++ # File locations are relative to the backend process, typically in a deployed context, such as in a Docker container, this will be the root ++ - type: file ++ target: ./examples/entities.yaml ++ ++ # Local example template ++ - type: file ++ target: ./examples/template/template.yaml ++ rules: ++ - allow: [Template] ++ ++ # Local example organizational data ++ - type: file ++ target: ./examples/org.yaml ++ rules: ++ - allow: [User, Group] +diff --git a/app-config.yaml b/app-config.yaml +index 1a45d40..ca52ec5 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 +@@ -40,3 +38,3 @@ integrations: + # 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 ++ # about setting up the GitHub integration here: https://backstage.io/docs/integrations/github/locations#configuration + token: ${GITHUB_TOKEN} +@@ -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 + +@@ -65,3 +66,5 @@ auth: + # see https://backstage.io/docs/auth/ to learn about auth providers +- providers: {} ++ providers: ++ # See https://backstage.io/docs/auth/guest/provider ++ guest: {} + +@@ -102 +105,9 @@ catalog: + # - allow: [User, Group] ++ ++kubernetes: ++ # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options ++ ++# see https://backstage.io/docs/permissions/getting-started for more on the permission framework ++permission: ++ # setting this to `false` will disable permissions ++ enabled: true +diff --git a/backstage.json b/backstage.json +index 78259a4..230d39d 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "1.8.0" ++ "version": "1.30.0" + } +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..80a963e 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,13 +33,15 @@ + "devDependencies": { +- "@backstage/cli": "^0.21.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/e2e-test-utils": "^0.1.1", ++ "@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": "^10.0.0", + "prettier": "^2.3.2", +- "typescript": "~4.6.4", +- "node-gyp": "^9.0.0" ++ "typescript": "~5.4.0" + }, + "resolutions": { +- "@types/react": "^17", +- "@types/react-dom": "^17" ++ "@types/react": "^18", ++ "@types/react-dom": "^18" + }, +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..839ff88 +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,27 @@ ++/* ++ * 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('/'); ++ ++ const enterButton = page.getByRole('button', { name: 'Enter' }); ++ await expect(enterButton).toBeVisible(); ++ await enterButton.click(); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index a085f61..346ffc1 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -13,34 +13,29 @@ + "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.5.10", ++ "@backstage/catalog-model": "^1.6.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/core-app-api": "^1.14.2", ++ "@backstage/core-components": "^0.14.10", ++ "@backstage/core-plugin-api": "^1.9.3", ++ "@backstage/integration-react": "^1.1.30", ++ "@backstage/plugin-api-docs": "^0.11.8", ++ "@backstage/plugin-catalog": "^1.22.0", ++ "@backstage/plugin-catalog-common": "^1.0.26", ++ "@backstage/plugin-catalog-graph": "^0.4.8", ++ "@backstage/plugin-catalog-import": "^0.12.2", ++ "@backstage/plugin-catalog-react": "^1.12.3", ++ "@backstage/plugin-kubernetes": "^0.11.13", ++ "@backstage/plugin-org": "^0.6.28", ++ "@backstage/plugin-permission-react": "^0.4.25", ++ "@backstage/plugin-scaffolder": "^1.24.0", ++ "@backstage/plugin-search": "^1.4.15", ++ "@backstage/plugin-search-react": "^1.7.14", ++ "@backstage/plugin-techdocs": "^1.10.8", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.13", ++ "@backstage/plugin-techdocs-react": "^1.2.7", ++ "@backstage/plugin-user-settings": "^0.8.11", ++ "@backstage/theme": "^0.5.6", + "@material-ui/core": "^4.12.2", +@@ -48,4 +43,4 @@ + "history": "^5.0.0", +- "react": "^17.0.2", +- "react-dom": "^17.0.2", ++ "react": "^18.0.2", ++ "react-dom": "^18.0.2", + "react-router": "^6.3.0", +@@ -55,12 +50,10 @@ + "devDependencies": { +- "@backstage/test-utils": "^1.2.2", +- "@testing-library/jest-dom": "^5.10.1", +- "@testing-library/react": "^12.1.3", ++ "@backstage/test-utils": "^1.5.10", ++ "@playwright/test": "^1.32.3", ++ "@testing-library/jest-dom": "^6.0.0", ++ "@testing-library/react": "^14.0.0", + "@testing-library/user-event": "^14.0.0", +- "@types/node": "^16.11.26", ++ "@testing-library/dom": "^9.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..18da7c4 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -8,5 +8,4 @@ + name="description" +- content="Backstage is an open platform for building developer portals" ++ content="Backstage is an open source framework for building developer portals" + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index 82bc479..ec8ba1d 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { renderWithEffects } from '@backstage/test-utils'; ++import { render, waitFor } from '@testing-library/react'; + import App from './App'; +@@ -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', + }, +@@ -22,4 +22,7 @@ describe('App', () => { + +- const rendered = await renderWithEffects(); +- expect(rendered.baseElement).toBeInTheDocument(); ++ const rendered = render(); ++ ++ await waitFor(() => { ++ expect(rendered.baseElement).toBeInTheDocument(); ++ }); + }); +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 8a53583..e24c468 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,6 +13,5 @@ import { + import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; ++import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +-import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -21,2 +20,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'; +@@ -27,5 +28,12 @@ import { Root } from './components/Root'; + +-import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; ++import { ++ AlertDisplay, ++ OAuthRequestDialog, ++ SignInPage, ++} 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 +45,6 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); + bind(apiDocsPlugin.externalRoutes, { +- createComponent: scaffolderPlugin.routes.root, ++ registerApi: catalogImportPlugin.routes.importPage, + }); +@@ -43,3 +52,10 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); ++ bind(orgPlugin.externalRoutes, { ++ catalogIndex: catalogPlugin.routes.catalogIndex, ++ }); ++ }, ++ components: { ++ SignInPage: props => , + }, +@@ -47,8 +63,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -60,5 +73,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -70,6 +85,9 @@ const routes = ( + } ++ path="/catalog-import" ++ element={ ++ ++ ++ ++ } + /> +- } /> + }> +@@ -78,2 +96,3 @@ const routes = ( + } /> ++ } /> + +@@ -81,4 +100,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -88,5 +107,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..853d175 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,22 +1,5 @@ +-/* +- * 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'; + import ExtensionIcon from '@material-ui/icons/Extension'; +-import MapIcon from '@material-ui/icons/MyLocation'; + import LibraryBooks from '@material-ui/icons/LibraryBooks'; +@@ -25,4 +8,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 +15,16 @@ 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'; ++import { MyGroupsSidebarItem } from '@backstage/plugin-org'; ++import GroupIcon from '@material-ui/icons/People'; + +@@ -56,3 +47,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -60,8 +51,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -76,17 +62,32 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ } to="/search"> ++ ++ + +- {/* Global nav, not org-specific */} +- +- +- +- +- {/* End global nav */} +- +- +- +- ++ }> ++ {/* Global nav, not org-specific */} ++ ++ ++ ++ ++ ++ {/* End global nav */} ++ ++ ++ {/* Items in this group will be scrollable if they run out of space */} ++ ++ + + +- ++ } ++ 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..6164c76 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,7 +27,5 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +-import { +- isGithubActionsAvailable, +- EntityGithubActionsContent, +-} from '@backstage/plugin-github-actions'; + import { +@@ -56,2 +38,32 @@ 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'; ++ ++import { ++ EntityKubernetesContent, ++ isKubernetesAvailable, ++} from '@backstage/plugin-kubernetes'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); + +@@ -61,5 +73,9 @@ const cicdContent = ( + +- +- +- ++ {/* ++ Here you can add support for different CI/CD services, for example ++ using @backstage-community/plugin-github-actions as follows: ++ ++ ++ ++ */} + +@@ -94,2 +110,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -110,2 +134,6 @@ const overviewContent = ( + ++ ++ ++ ++ + +@@ -129,2 +157,10 @@ const serviceEntityPage = ( + ++ ++ ++ ++ + +@@ -152,3 +188,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -167,2 +203,10 @@ const websiteEntityPage = ( + ++ ++ ++ ++ + +@@ -179,3 +223,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -198,3 +242,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -225,2 +269,5 @@ const apiPage = ( + ++ ++ ++ + +@@ -276,5 +323,8 @@ const groupPage = ( + +- ++ + + ++ ++ ++ + +@@ -292,3 +342,9 @@ const systemPage = ( + +- ++ ++ ++ ++ ++ ++ ++ + +@@ -304,3 +360,19 @@ const systemPage = ( + +- ++ + +@@ -317,2 +389,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/app/src/index.tsx b/packages/app/src/index.tsx +index b16aaf7..d875c77 100644 +--- a/packages/app/src/index.tsx ++++ b/packages/app/src/index.tsx +@@ -2,5 +2,5 @@ import '@backstage/cli/asset-types'; + import React from 'react'; +-import ReactDOM from 'react-dom'; ++import ReactDOM from 'react-dom/client'; + import App from './App'; + +-ReactDOM.render(, document.getElementById('root')); ++ReactDOM.createRoot(document.getElementById('root')!).render(); +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..bef67b5 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,15 @@ 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 ++ ++# This will include the examples, if you don't need these simply remove this line ++COPY --chown=node:node examples ./examples + + # 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..6a14abc 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,32 +6,43 @@ + "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", +- "dockerode": "^3.3.1", +- "express": "^4.17.1", +- "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "@backstage/backend-common": "^0.24.0", ++ "@backstage/backend-defaults": "^0.4.2", ++ "@backstage/backend-tasks": "^0.6.0", ++ "@backstage/config": "^1.2.0", ++ "@backstage/plugin-app-backend": "^0.3.72", ++ "@backstage/plugin-auth-backend": "^0.22.10", ++ "@backstage/plugin-auth-backend-module-github-provider": "^0.1.20", ++ "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.9", ++ "@backstage/plugin-auth-node": "^0.5.0", ++ "@backstage/plugin-catalog-backend": "^1.25.0", ++ "@backstage/plugin-catalog-backend-module-logs": "^0.0.2", ++ "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.21", ++ "@backstage/plugin-kubernetes-backend": "^0.18.4", ++ "@backstage/plugin-permission-backend": "^0.5.47", ++ "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.1.20", ++ "@backstage/plugin-permission-common": "^0.8.1", ++ "@backstage/plugin-permission-node": "^0.8.1", ++ "@backstage/plugin-proxy-backend": "^0.5.4", ++ "@backstage/plugin-scaffolder-backend": "^1.24.0", ++ "@backstage/plugin-search-backend": "^1.5.15", ++ "@backstage/plugin-search-backend-module-catalog": "^0.2.0", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.33", ++ "@backstage/plugin-search-backend-module-techdocs": "^0.2.0", ++ "@backstage/plugin-search-backend-node": "^1.3.0", ++ "@backstage/plugin-techdocs-backend": "^1.10.10", ++ "app": "link:../app", ++ "better-sqlite3": "^9.0.0", ++ "node-gyp": "^10.0.0", ++ "pg": "^8.11.3", + "winston": "^3.2.1" +@@ -39,6 +50,6 @@ + "devDependencies": { +- "@backstage/cli": "^0.9.0", +- "@types/dockerode": "^3.3.0", ++ "@backstage/cli": "^0.27.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.test.ts b/packages/backend/src/index.test.ts +deleted file mode 100644 +index 7814b8c..0000000 +--- a/packages/backend/src/index.test.ts ++++ /dev/null +@@ -1,8 +0,0 @@ +-import { PluginEnvironment } from './types'; +- +-describe('test', () => { +- it('unbreaks the test runner', () => { +- const unbreaker = {} as PluginEnvironment; +- expect(unbreaker).toBeTruthy(); +- }); +-}); +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index f2b14b2..f57d4c5 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -8,81 +8,47 @@ + +-import Router from 'express-promise-router'; +-import { +- createServiceBuilder, +- loadBackendConfig, +- getRootLogger, +- useHotMemoize, +- notFoundHandler, +- CacheManager, +- DatabaseManager, +- SingleHostDiscovery, +- UrlReaders, +-} from '@backstage/backend-common'; +-import { Config } from '@backstage/config'; +-import app from './plugins/app'; +-import auth from './plugins/auth'; +-import catalog from './plugins/catalog'; +-import scaffolder from './plugins/scaffolder'; +-import proxy from './plugins/proxy'; +-import techdocs from './plugins/techdocs'; +-import search from './plugins/search'; +-import { PluginEnvironment } from './types'; ++import { createBackend } from '@backstage/backend-defaults'; + +-function makeCreateEnv(config: Config) { +- const root = getRootLogger(); +- const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++const backend = createBackend(); + +- root.info(`Created UrlReader ${reader}`); ++backend.add(import('@backstage/plugin-app-backend/alpha')); ++backend.add(import('@backstage/plugin-proxy-backend/alpha')); ++backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); ++backend.add(import('@backstage/plugin-techdocs-backend/alpha')); + +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++// auth plugin ++backend.add(import('@backstage/plugin-auth-backend')); ++// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin ++backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); ++// See https://backstage.io/docs/auth/guest/provider + +- return (plugin: string): PluginEnvironment => { +- const logger = root.child({ type: 'plugin', plugin }); +- const database = databaseManager.forPlugin(plugin); +- const cache = cacheManager.forPlugin(plugin); +- return { logger, database, cache, config, reader, discovery }; +- }; +-} ++// catalog plugin ++backend.add(import('@backstage/plugin-catalog-backend/alpha')); ++backend.add( ++ import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ++); + +-async function main() { +- const config = await loadBackendConfig({ +- argv: process.argv, +- logger: getRootLogger(), +- }); +- const createEnv = makeCreateEnv(config); ++// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors ++backend.add(import('@backstage/plugin-catalog-backend-module-logs')); + +- const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); +- const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); +- const authEnv = useHotMemoize(module, () => createEnv('auth')); +- const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); +- const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); +- const searchEnv = useHotMemoize(module, () => createEnv('search')); +- const appEnv = useHotMemoize(module, () => createEnv('app')); ++// permission plugin ++backend.add(import('@backstage/plugin-permission-backend/alpha')); ++// See https://backstage.io/docs/permissions/getting-started for how to create your own permission policy ++backend.add( ++ import('@backstage/plugin-permission-backend-module-allow-all-policy'), ++); + +- const apiRouter = Router(); +- apiRouter.use('/catalog', await catalog(catalogEnv)); +- apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); +- apiRouter.use('/auth', await auth(authEnv)); +- apiRouter.use('/techdocs', await techdocs(techdocsEnv)); +- apiRouter.use('/proxy', await proxy(proxyEnv)); +- apiRouter.use('/search', await search(searchEnv)); +- apiRouter.use(notFoundHandler()); ++// search plugin ++backend.add(import('@backstage/plugin-search-backend/alpha')); + +- const service = createServiceBuilder(module) +- .loadConfig(config) +- .addRouter('/api', apiRouter) +- .addRouter('', await app(appEnv)); ++// search engine ++// See https://backstage.io/docs/features/search/search-engines ++backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); + +- await service.start().catch(err => { +- console.log(err); +- process.exit(1); +- }); +-} ++// search collators ++backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); ++backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); + +-module.hot?.accept(); +-main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); +- process.exit(1); +-}); ++// kubernetes ++backend.add(import('@backstage/plugin-kubernetes-backend/alpha')); ++ ++backend.start(); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +deleted file mode 100644 +index 07fb04f..0000000 +--- a/packages/backend/src/plugins/app.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { createRouter } from '@backstage/plugin-app-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +-}: PluginEnvironment): Promise { +- return await createRouter({ +- logger, +- config, +- appPackageName: 'app', +- }); +-} +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +deleted file mode 100644 +index 5216510..0000000 +--- a/packages/backend/src/plugins/auth.ts ++++ /dev/null +@@ -1,12 +0,0 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, database, discovery }); +-} +diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts +deleted file mode 100644 +index 876cb6b..0000000 +--- a/packages/backend/src/plugins/catalog.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +-import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const builder = await CatalogBuilder.create(env); +- builder.addProcessor(new ScaffolderEntitiesProcessor()); +- const { processingEngine, router } = await builder.build(); +- await processingEngine.start(); +- return router; +-} +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +deleted file mode 100644 +index 506f6d9..0000000 +--- a/packages/backend/src/plugins/proxy.ts ++++ /dev/null +@@ -1,11 +0,0 @@ +-import { createRouter } from '@backstage/plugin-proxy-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); +-} +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +deleted file mode 100644 +index 6be2e97..0000000 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ /dev/null +@@ -1,27 +0,0 @@ +-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'; +-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 }); +- +- return await createRouter({ +- containerRunner, +- logger, +- config, +- database, +- catalogClient, +- reader, +- }); +-} +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +deleted file mode 100644 +index 7fc317d..0000000 +--- a/packages/backend/src/plugins/search.ts ++++ /dev/null +@@ -1,39 +0,0 @@ +-import { useHotCleanup } from '@backstage/backend-common'; +-import { createRouter } from '@backstage/plugin-search-backend'; +-import { +- IndexBuilder, +- LunrSearchEngine, +-} from '@backstage/plugin-search-backend-node'; +-import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +- +-export default async function createPlugin({ +- logger, +- discovery, +- config, +-}: PluginEnvironment) { +- // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); +- +- // Collators are responsible for gathering documents known to plugins. This +- // particular collator gathers entities from the software catalog. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { discovery }), +- }); +- +- // The scheduler controls when documents are gathered from collators and sent +- // to the search engine for indexing. +- const { scheduler } = await indexBuilder.build(); +- +- // 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()); +- +- return await createRouter({ +- engine: indexBuilder.getSearchEngine(), +- logger, +- }); +-} +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +deleted file mode 100644 +index 906d86d..0000000 +--- a/packages/backend/src/plugins/techdocs.ts ++++ /dev/null +@@ -1,53 +0,0 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; +-import { +- createRouter, +- Generators, +- Preparers, +- Publisher, +-} from '@backstage/plugin-techdocs-backend'; +-import Docker from 'dockerode'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +-}: PluginEnvironment): Promise { +- // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, +- }); +- +- // Docker client (conditionally) used by the generators, based on techdocs.generators config. +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- +- // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, +- containerRunner, +- }); +- +- // Publisher is used for +- // 1. Publishing generated files to storage +- // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, +- }); +- +- // checks if the publisher is working and logs the result +- await publisher.getReadiness(); +- +- return await createRouter({ +- preparers, +- generators, +- publisher, +- logger, +- config, +- discovery, +- }); +-} +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +deleted file mode 100644 +index 6c78a2a..0000000 +--- a/packages/backend/src/types.ts ++++ /dev/null +@@ -1,17 +0,0 @@ +-import { Logger } from 'winston'; +-import { Config } from '@backstage/config'; +-import { +- PluginCacheManager, +- PluginDatabaseManager, +- PluginEndpointDiscovery, +- UrlReader, +-} from '@backstage/backend-common'; +- +-export type PluginEnvironment = { +- logger: Logger; +- database: PluginDatabaseManager; +- cache: PluginCacheManager; +- config: Config; +- reader: UrlReader; +- discovery: PluginEndpointDiscovery; +-}; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..733be13 +--- /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 dev', ++ 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..c37fc61 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1,3 @@ ++# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. ++# yarn lockfile v1 ++ diff --git a/diffs/0.4.5..0.5.18.diff b/diffs/0.4.5..0.5.18.diff new file mode 100644 index 00000000..abcb2c1d --- /dev/null +++ b/diffs/0.4.5..0.5.18.diff @@ -0,0 +1,2088 @@ +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..5d426f5 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,51 @@ 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/apis/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 ++ ++auth: ++ providers: ++ guest: {} ++ ++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: ++ # Local example data, replace this with your production config, these are intended for demo use only. ++ # File locations are relative to the backend process, typically in a deployed context, such as in a Docker container, this will be the root ++ - type: file ++ target: ./examples/entities.yaml ++ ++ # Local example template ++ - type: file ++ target: ./examples/template/template.yaml ++ rules: ++ - allow: [Template] ++ ++ # Local example organizational data ++ - type: file ++ target: ./examples/org.yaml ++ rules: ++ - allow: [User, Group] +diff --git a/app-config.yaml b/app-config.yaml +index aac1eb1..ca52ec5 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/integrations/github/locations#configuration + 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 + +@@ -53,49 +66,48 @@ auth: + # see https://backstage.io/docs/auth/ to learn about auth providers +- providers: {} ++ providers: ++ # See https://backstage.io/docs/auth/guest/provider ++ guest: {} + + 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 ++ # 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 ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml ++ rules: ++ - allow: [Template] + +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml ++ rules: ++ - allow: [User, Group] + +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml + +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.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] + +- # 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 +- 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 +- rules: +- - allow: [Template] ++kubernetes: ++ # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options ++ ++# see https://backstage.io/docs/permissions/getting-started for more on the permission framework ++permission: ++ # setting this to `false` will disable permissions ++ enabled: true +diff --git a/backstage.json b/backstage.json +index 77e9232..230d39d 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "0.4.5" ++ "version": "1.30.0" + } +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..80a963e 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.27.0", ++ "@backstage/e2e-test-utils": "^0.1.1", ++ "@playwright/test": "^1.32.3", ++ "@spotify/prettier-config": "^12.0.0", ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^10.0.0", ++ "prettier": "^2.3.2", ++ "typescript": "~5.4.0" ++ }, ++ "resolutions": { ++ "@types/react": "^18", ++ "@types/react-dom": "^18" + }, +@@ -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..839ff88 +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,27 @@ ++/* ++ * 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('/'); ++ ++ const enterButton = page.getByRole('button', { name: 'Enter' }); ++ await expect(enterButton).toBeVisible(); ++ await enterButton.click(); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 6d87eb0..346ffc1 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,23 +5,37 @@ + "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.5.10", ++ "@backstage/catalog-model": "^1.6.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/core-app-api": "^1.14.2", ++ "@backstage/core-components": "^0.14.10", ++ "@backstage/core-plugin-api": "^1.9.3", ++ "@backstage/integration-react": "^1.1.30", ++ "@backstage/plugin-api-docs": "^0.11.8", ++ "@backstage/plugin-catalog": "^1.22.0", ++ "@backstage/plugin-catalog-common": "^1.0.26", ++ "@backstage/plugin-catalog-graph": "^0.4.8", ++ "@backstage/plugin-catalog-import": "^0.12.2", ++ "@backstage/plugin-catalog-react": "^1.12.3", ++ "@backstage/plugin-kubernetes": "^0.11.13", ++ "@backstage/plugin-org": "^0.6.28", ++ "@backstage/plugin-permission-react": "^0.4.25", ++ "@backstage/plugin-scaffolder": "^1.24.0", ++ "@backstage/plugin-search": "^1.4.15", ++ "@backstage/plugin-search-react": "^1.7.14", ++ "@backstage/plugin-techdocs": "^1.10.8", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.13", ++ "@backstage/plugin-techdocs-react": "^1.2.7", ++ "@backstage/plugin-user-settings": "^0.8.11", ++ "@backstage/theme": "^0.5.6", + "@material-ui/core": "^4.12.2", +@@ -29,30 +43,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": "^18.0.2", ++ "react-dom": "^18.0.2", ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@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", ++ "@backstage/test-utils": "^1.5.10", ++ "@playwright/test": "^1.32.3", ++ "@testing-library/jest-dom": "^6.0.0", ++ "@testing-library/react": "^14.0.0", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^9.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..18da7c4 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -8,5 +8,4 @@ + name="description" +- content="Backstage is an open platform for building developer portals" ++ content="Backstage is an open source framework for building developer portals" + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index b94cac7..ec8ba1d 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { renderWithEffects } from '@backstage/test-utils'; ++import { render, waitFor } from '@testing-library/react'; + import App from './App'; +@@ -22,4 +22,7 @@ describe('App', () => { + +- const rendered = await renderWithEffects(); +- expect(rendered.baseElement).toBeInTheDocument(); ++ const rendered = render(); ++ ++ await waitFor(() => { ++ expect(rendered.baseElement).toBeInTheDocument(); ++ }); + }); +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 8a53583..e24c468 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,6 +13,5 @@ import { + import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; ++import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +-import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -21,2 +20,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'; +@@ -27,5 +28,12 @@ import { Root } from './components/Root'; + +-import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; ++import { ++ AlertDisplay, ++ OAuthRequestDialog, ++ SignInPage, ++} 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 +45,6 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); + bind(apiDocsPlugin.externalRoutes, { +- createComponent: scaffolderPlugin.routes.root, ++ registerApi: catalogImportPlugin.routes.importPage, + }); +@@ -43,3 +52,10 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); ++ bind(orgPlugin.externalRoutes, { ++ catalogIndex: catalogPlugin.routes.catalogIndex, ++ }); ++ }, ++ components: { ++ SignInPage: props => , + }, +@@ -47,8 +63,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -60,5 +73,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -70,6 +85,9 @@ const routes = ( + } ++ path="/catalog-import" ++ element={ ++ ++ ++ ++ } + /> +- } /> + }> +@@ -78,2 +96,3 @@ const routes = ( + } /> ++ } /> + +@@ -81,4 +100,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -88,5 +107,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..853d175 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,22 +1,5 @@ +-/* +- * 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'; + import ExtensionIcon from '@material-ui/icons/Extension'; +-import MapIcon from '@material-ui/icons/MyLocation'; + import LibraryBooks from '@material-ui/icons/LibraryBooks'; +@@ -25,4 +8,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 +15,16 @@ 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'; ++import { MyGroupsSidebarItem } from '@backstage/plugin-org'; ++import GroupIcon from '@material-ui/icons/People'; + +@@ -56,3 +47,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -60,8 +51,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -76,17 +62,32 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ } to="/search"> ++ ++ + +- {/* Global nav, not org-specific */} +- +- +- +- +- {/* End global nav */} +- +- +- +- ++ }> ++ {/* Global nav, not org-specific */} ++ ++ ++ ++ ++ ++ {/* End global nav */} ++ ++ ++ {/* Items in this group will be scrollable if they run out of space */} ++ ++ + + +- ++ } ++ 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..6164c76 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,7 +27,5 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +-import { +- isGithubActionsAvailable, +- EntityGithubActionsContent, +-} from '@backstage/plugin-github-actions'; + import { +@@ -56,2 +38,32 @@ 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'; ++ ++import { ++ EntityKubernetesContent, ++ isKubernetesAvailable, ++} from '@backstage/plugin-kubernetes'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); + +@@ -61,5 +73,9 @@ const cicdContent = ( + +- +- +- ++ {/* ++ Here you can add support for different CI/CD services, for example ++ using @backstage-community/plugin-github-actions as follows: ++ ++ ++ ++ */} + +@@ -94,2 +110,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -110,2 +134,6 @@ const overviewContent = ( + ++ ++ ++ ++ + +@@ -129,2 +157,10 @@ const serviceEntityPage = ( + ++ ++ ++ ++ + +@@ -152,3 +188,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -167,2 +203,10 @@ const websiteEntityPage = ( + ++ ++ ++ ++ + +@@ -179,3 +223,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -198,3 +242,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -225,2 +269,5 @@ const apiPage = ( + ++ ++ ++ + +@@ -276,5 +323,8 @@ const groupPage = ( + +- ++ + + ++ ++ ++ + +@@ -292,3 +342,9 @@ const systemPage = ( + +- ++ ++ ++ ++ ++ ++ ++ + +@@ -304,3 +360,19 @@ const systemPage = ( + +- ++ + +@@ -317,2 +389,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/app/src/index.tsx b/packages/app/src/index.tsx +index b16aaf7..d875c77 100644 +--- a/packages/app/src/index.tsx ++++ b/packages/app/src/index.tsx +@@ -2,5 +2,5 @@ import '@backstage/cli/asset-types'; + import React from 'react'; +-import ReactDOM from 'react-dom'; ++import ReactDOM from 'react-dom/client'; + import App from './App'; + +-ReactDOM.render(, document.getElementById('root')); ++ReactDOM.createRoot(document.getElementById('root')!).render(); +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..bef67b5 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,15 @@ 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 ++ ++# This will include the examples, if you don't need these simply remove this line ++COPY --chown=node:node examples ./examples + + # 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..6a14abc 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,32 +6,43 @@ + "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", +- "dockerode": "^3.3.1", +- "express": "^4.17.1", +- "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "@backstage/backend-common": "^0.24.0", ++ "@backstage/backend-defaults": "^0.4.2", ++ "@backstage/backend-tasks": "^0.6.0", ++ "@backstage/config": "^1.2.0", ++ "@backstage/plugin-app-backend": "^0.3.72", ++ "@backstage/plugin-auth-backend": "^0.22.10", ++ "@backstage/plugin-auth-backend-module-github-provider": "^0.1.20", ++ "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.9", ++ "@backstage/plugin-auth-node": "^0.5.0", ++ "@backstage/plugin-catalog-backend": "^1.25.0", ++ "@backstage/plugin-catalog-backend-module-logs": "^0.0.2", ++ "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.21", ++ "@backstage/plugin-kubernetes-backend": "^0.18.4", ++ "@backstage/plugin-permission-backend": "^0.5.47", ++ "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.1.20", ++ "@backstage/plugin-permission-common": "^0.8.1", ++ "@backstage/plugin-permission-node": "^0.8.1", ++ "@backstage/plugin-proxy-backend": "^0.5.4", ++ "@backstage/plugin-scaffolder-backend": "^1.24.0", ++ "@backstage/plugin-search-backend": "^1.5.15", ++ "@backstage/plugin-search-backend-module-catalog": "^0.2.0", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.33", ++ "@backstage/plugin-search-backend-module-techdocs": "^0.2.0", ++ "@backstage/plugin-search-backend-node": "^1.3.0", ++ "@backstage/plugin-techdocs-backend": "^1.10.10", ++ "app": "link:../app", ++ "better-sqlite3": "^9.0.0", ++ "node-gyp": "^10.0.0", ++ "pg": "^8.11.3", + "winston": "^3.2.1" +@@ -39,6 +50,6 @@ + "devDependencies": { +- "@backstage/cli": "^0.9.1", +- "@types/dockerode": "^3.3.0", ++ "@backstage/cli": "^0.27.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.test.ts b/packages/backend/src/index.test.ts +deleted file mode 100644 +index 7814b8c..0000000 +--- a/packages/backend/src/index.test.ts ++++ /dev/null +@@ -1,8 +0,0 @@ +-import { PluginEnvironment } from './types'; +- +-describe('test', () => { +- it('unbreaks the test runner', () => { +- const unbreaker = {} as PluginEnvironment; +- expect(unbreaker).toBeTruthy(); +- }); +-}); +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index f2b14b2..f57d4c5 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -8,81 +8,47 @@ + +-import Router from 'express-promise-router'; +-import { +- createServiceBuilder, +- loadBackendConfig, +- getRootLogger, +- useHotMemoize, +- notFoundHandler, +- CacheManager, +- DatabaseManager, +- SingleHostDiscovery, +- UrlReaders, +-} from '@backstage/backend-common'; +-import { Config } from '@backstage/config'; +-import app from './plugins/app'; +-import auth from './plugins/auth'; +-import catalog from './plugins/catalog'; +-import scaffolder from './plugins/scaffolder'; +-import proxy from './plugins/proxy'; +-import techdocs from './plugins/techdocs'; +-import search from './plugins/search'; +-import { PluginEnvironment } from './types'; ++import { createBackend } from '@backstage/backend-defaults'; + +-function makeCreateEnv(config: Config) { +- const root = getRootLogger(); +- const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++const backend = createBackend(); + +- root.info(`Created UrlReader ${reader}`); ++backend.add(import('@backstage/plugin-app-backend/alpha')); ++backend.add(import('@backstage/plugin-proxy-backend/alpha')); ++backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); ++backend.add(import('@backstage/plugin-techdocs-backend/alpha')); + +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); ++// auth plugin ++backend.add(import('@backstage/plugin-auth-backend')); ++// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin ++backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); ++// See https://backstage.io/docs/auth/guest/provider + +- return (plugin: string): PluginEnvironment => { +- const logger = root.child({ type: 'plugin', plugin }); +- const database = databaseManager.forPlugin(plugin); +- const cache = cacheManager.forPlugin(plugin); +- return { logger, database, cache, config, reader, discovery }; +- }; +-} ++// catalog plugin ++backend.add(import('@backstage/plugin-catalog-backend/alpha')); ++backend.add( ++ import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ++); + +-async function main() { +- const config = await loadBackendConfig({ +- argv: process.argv, +- logger: getRootLogger(), +- }); +- const createEnv = makeCreateEnv(config); ++// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors ++backend.add(import('@backstage/plugin-catalog-backend-module-logs')); + +- const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); +- const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); +- const authEnv = useHotMemoize(module, () => createEnv('auth')); +- const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); +- const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); +- const searchEnv = useHotMemoize(module, () => createEnv('search')); +- const appEnv = useHotMemoize(module, () => createEnv('app')); ++// permission plugin ++backend.add(import('@backstage/plugin-permission-backend/alpha')); ++// See https://backstage.io/docs/permissions/getting-started for how to create your own permission policy ++backend.add( ++ import('@backstage/plugin-permission-backend-module-allow-all-policy'), ++); + +- const apiRouter = Router(); +- apiRouter.use('/catalog', await catalog(catalogEnv)); +- apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); +- apiRouter.use('/auth', await auth(authEnv)); +- apiRouter.use('/techdocs', await techdocs(techdocsEnv)); +- apiRouter.use('/proxy', await proxy(proxyEnv)); +- apiRouter.use('/search', await search(searchEnv)); +- apiRouter.use(notFoundHandler()); ++// search plugin ++backend.add(import('@backstage/plugin-search-backend/alpha')); + +- const service = createServiceBuilder(module) +- .loadConfig(config) +- .addRouter('/api', apiRouter) +- .addRouter('', await app(appEnv)); ++// search engine ++// See https://backstage.io/docs/features/search/search-engines ++backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); + +- await service.start().catch(err => { +- console.log(err); +- process.exit(1); +- }); +-} ++// search collators ++backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); ++backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); + +-module.hot?.accept(); +-main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); +- process.exit(1); +-}); ++// kubernetes ++backend.add(import('@backstage/plugin-kubernetes-backend/alpha')); ++ ++backend.start(); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +deleted file mode 100644 +index 07fb04f..0000000 +--- a/packages/backend/src/plugins/app.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { createRouter } from '@backstage/plugin-app-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +-}: PluginEnvironment): Promise { +- return await createRouter({ +- logger, +- config, +- appPackageName: 'app', +- }); +-} +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +deleted file mode 100644 +index 5216510..0000000 +--- a/packages/backend/src/plugins/auth.ts ++++ /dev/null +@@ -1,12 +0,0 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, database, discovery }); +-} +diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts +deleted file mode 100644 +index 876cb6b..0000000 +--- a/packages/backend/src/plugins/catalog.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +-import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const builder = await CatalogBuilder.create(env); +- builder.addProcessor(new ScaffolderEntitiesProcessor()); +- const { processingEngine, router } = await builder.build(); +- await processingEngine.start(); +- return router; +-} +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +deleted file mode 100644 +index 506f6d9..0000000 +--- a/packages/backend/src/plugins/proxy.ts ++++ /dev/null +@@ -1,11 +0,0 @@ +-import { createRouter } from '@backstage/plugin-proxy-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); +-} +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +deleted file mode 100644 +index 6be2e97..0000000 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ /dev/null +@@ -1,27 +0,0 @@ +-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'; +-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 }); +- +- return await createRouter({ +- containerRunner, +- logger, +- config, +- database, +- catalogClient, +- reader, +- }); +-} +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +deleted file mode 100644 +index 63e1962..0000000 +--- a/packages/backend/src/plugins/search.ts ++++ /dev/null +@@ -1,46 +0,0 @@ +-import { useHotCleanup } from '@backstage/backend-common'; +-import { createRouter } from '@backstage/plugin-search-backend'; +-import { +- IndexBuilder, +- LunrSearchEngine, +-} from '@backstage/plugin-search-backend-node'; +-import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend'; +- +-export default async function createPlugin({ +- logger, +- discovery, +- config, +-}: PluginEnvironment) { +- // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); +- +- // Collators are responsible for gathering documents known to plugins. This +- // collator gathers entities from the software catalog. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { discovery }), +- }); +- +- // collator gathers entities from techdocs. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultTechDocsCollator.fromConfig(config, { discovery, logger }), +- }); +- +- // The scheduler controls when documents are gathered from collators and sent +- // to the search engine for indexing. +- const { scheduler } = await indexBuilder.build(); +- +- // 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()); +- +- return await createRouter({ +- engine: indexBuilder.getSearchEngine(), +- logger, +- }); +-} +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +deleted file mode 100644 +index 906d86d..0000000 +--- a/packages/backend/src/plugins/techdocs.ts ++++ /dev/null +@@ -1,53 +0,0 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; +-import { +- createRouter, +- Generators, +- Preparers, +- Publisher, +-} from '@backstage/plugin-techdocs-backend'; +-import Docker from 'dockerode'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +-}: PluginEnvironment): Promise { +- // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, +- }); +- +- // Docker client (conditionally) used by the generators, based on techdocs.generators config. +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- +- // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, +- containerRunner, +- }); +- +- // Publisher is used for +- // 1. Publishing generated files to storage +- // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, +- }); +- +- // checks if the publisher is working and logs the result +- await publisher.getReadiness(); +- +- return await createRouter({ +- preparers, +- generators, +- publisher, +- logger, +- config, +- discovery, +- }); +-} +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +deleted file mode 100644 +index 6c78a2a..0000000 +--- a/packages/backend/src/types.ts ++++ /dev/null +@@ -1,17 +0,0 @@ +-import { Logger } from 'winston'; +-import { Config } from '@backstage/config'; +-import { +- PluginCacheManager, +- PluginDatabaseManager, +- PluginEndpointDiscovery, +- UrlReader, +-} from '@backstage/backend-common'; +- +-export type PluginEnvironment = { +- logger: Logger; +- database: PluginDatabaseManager; +- cache: PluginCacheManager; +- config: Config; +- reader: UrlReader; +- discovery: PluginEndpointDiscovery; +-}; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..733be13 +--- /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 dev', ++ 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..c37fc61 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1,3 @@ ++# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. ++# yarn lockfile v1 ++ diff --git a/diffs/0.4.6..0.5.18.diff b/diffs/0.4.6..0.5.18.diff new file mode 100644 index 00000000..6917cb36 --- /dev/null +++ b/diffs/0.4.6..0.5.18.diff @@ -0,0 +1,2101 @@ +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..5d426f5 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,51 @@ 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/apis/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 ++ ++auth: ++ providers: ++ guest: {} ++ ++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: ++ # Local example data, replace this with your production config, these are intended for demo use only. ++ # File locations are relative to the backend process, typically in a deployed context, such as in a Docker container, this will be the root ++ - type: file ++ target: ./examples/entities.yaml ++ ++ # Local example template ++ - type: file ++ target: ./examples/template/template.yaml ++ rules: ++ - allow: [Template] ++ ++ # Local example organizational data ++ - type: file ++ target: ./examples/org.yaml ++ rules: ++ - allow: [User, Group] +diff --git a/app-config.yaml b/app-config.yaml +index 2999615..ca52ec5 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/integrations/github/locations#configuration + 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 + +@@ -58,49 +66,48 @@ auth: + # see https://backstage.io/docs/auth/ to learn about auth providers +- providers: {} ++ providers: ++ # See https://backstage.io/docs/auth/guest/provider ++ guest: {} + + 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 ++ # 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 ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml ++ rules: ++ - allow: [Template] + +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml ++ rules: ++ - allow: [User, Group] + +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml + +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.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] + +- # 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 +- 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 +- rules: +- - allow: [Template] ++kubernetes: ++ # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options ++ ++# see https://backstage.io/docs/permissions/getting-started for more on the permission framework ++permission: ++ # setting this to `false` will disable permissions ++ enabled: true +diff --git a/backstage.json b/backstage.json +index 1062ff8..230d39d 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "0.4.6" ++ "version": "1.30.0" + } +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..80a963e 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.27.0", ++ "@backstage/e2e-test-utils": "^0.1.1", ++ "@playwright/test": "^1.32.3", ++ "@spotify/prettier-config": "^12.0.0", ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^10.0.0", ++ "prettier": "^2.3.2", ++ "typescript": "~5.4.0" ++ }, ++ "resolutions": { ++ "@types/react": "^18", ++ "@types/react-dom": "^18" + }, +@@ -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..839ff88 +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,27 @@ ++/* ++ * 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('/'); ++ ++ const enterButton = page.getByRole('button', { name: 'Enter' }); ++ await expect(enterButton).toBeVisible(); ++ await enterButton.click(); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 9f1021a..346ffc1 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,23 +5,37 @@ + "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.5.10", ++ "@backstage/catalog-model": "^1.6.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/core-app-api": "^1.14.2", ++ "@backstage/core-components": "^0.14.10", ++ "@backstage/core-plugin-api": "^1.9.3", ++ "@backstage/integration-react": "^1.1.30", ++ "@backstage/plugin-api-docs": "^0.11.8", ++ "@backstage/plugin-catalog": "^1.22.0", ++ "@backstage/plugin-catalog-common": "^1.0.26", ++ "@backstage/plugin-catalog-graph": "^0.4.8", ++ "@backstage/plugin-catalog-import": "^0.12.2", ++ "@backstage/plugin-catalog-react": "^1.12.3", ++ "@backstage/plugin-kubernetes": "^0.11.13", ++ "@backstage/plugin-org": "^0.6.28", ++ "@backstage/plugin-permission-react": "^0.4.25", ++ "@backstage/plugin-scaffolder": "^1.24.0", ++ "@backstage/plugin-search": "^1.4.15", ++ "@backstage/plugin-search-react": "^1.7.14", ++ "@backstage/plugin-techdocs": "^1.10.8", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.13", ++ "@backstage/plugin-techdocs-react": "^1.2.7", ++ "@backstage/plugin-user-settings": "^0.8.11", ++ "@backstage/theme": "^0.5.6", + "@material-ui/core": "^4.12.2", +@@ -29,30 +43,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": "^18.0.2", ++ "react-dom": "^18.0.2", ++ "react-router": "^6.3.0", ++ "react-router-dom": "^6.3.0", ++ "react-use": "^17.2.4" + }, + "devDependencies": { +- "@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", ++ "@backstage/test-utils": "^1.5.10", ++ "@playwright/test": "^1.32.3", ++ "@testing-library/jest-dom": "^6.0.0", ++ "@testing-library/react": "^14.0.0", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^9.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..18da7c4 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -8,5 +8,4 @@ + name="description" +- content="Backstage is an open platform for building developer portals" ++ content="Backstage is an open source framework for building developer portals" + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index b94cac7..ec8ba1d 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { renderWithEffects } from '@backstage/test-utils'; ++import { render, waitFor } from '@testing-library/react'; + import App from './App'; +@@ -22,4 +22,7 @@ describe('App', () => { + +- const rendered = await renderWithEffects(); +- expect(rendered.baseElement).toBeInTheDocument(); ++ const rendered = render(); ++ ++ await waitFor(() => { ++ expect(rendered.baseElement).toBeInTheDocument(); ++ }); + }); +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 8a53583..e24c468 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,6 +13,5 @@ import { + import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; ++import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +-import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -21,2 +20,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'; +@@ -27,5 +28,12 @@ import { Root } from './components/Root'; + +-import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; ++import { ++ AlertDisplay, ++ OAuthRequestDialog, ++ SignInPage, ++} 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 +45,6 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); + bind(apiDocsPlugin.externalRoutes, { +- createComponent: scaffolderPlugin.routes.root, ++ registerApi: catalogImportPlugin.routes.importPage, + }); +@@ -43,3 +52,10 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); ++ bind(orgPlugin.externalRoutes, { ++ catalogIndex: catalogPlugin.routes.catalogIndex, ++ }); ++ }, ++ components: { ++ SignInPage: props => , + }, +@@ -47,8 +63,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -60,5 +73,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -70,6 +85,9 @@ const routes = ( + } ++ path="/catalog-import" ++ element={ ++ ++ ++ ++ } + /> +- } /> + }> +@@ -78,2 +96,3 @@ const routes = ( + } /> ++ } /> + +@@ -81,4 +100,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -88,5 +107,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..853d175 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,22 +1,5 @@ +-/* +- * 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'; + import ExtensionIcon from '@material-ui/icons/Extension'; +-import MapIcon from '@material-ui/icons/MyLocation'; + import LibraryBooks from '@material-ui/icons/LibraryBooks'; +@@ -25,18 +8,23 @@ 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'; ++import { MyGroupsSidebarItem } from '@backstage/plugin-org'; ++import GroupIcon from '@material-ui/icons/People'; + +@@ -59,3 +47,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -63,8 +51,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -79,19 +62,32 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ } to="/search"> + +- ++ + +- {/* Global nav, not org-specific */} +- +- +- +- +- {/* End global nav */} +- +- +- +- ++ }> ++ {/* Global nav, not org-specific */} ++ ++ ++ ++ ++ ++ {/* End global nav */} ++ ++ ++ {/* Items in this group will be scrollable if they run out of space */} ++ ++ + + +- ++ } ++ 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..6164c76 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,7 +27,5 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +-import { +- isGithubActionsAvailable, +- EntityGithubActionsContent, +-} from '@backstage/plugin-github-actions'; + import { +@@ -56,2 +38,32 @@ 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'; ++ ++import { ++ EntityKubernetesContent, ++ isKubernetesAvailable, ++} from '@backstage/plugin-kubernetes'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); + +@@ -61,5 +73,9 @@ const cicdContent = ( + +- +- +- ++ {/* ++ Here you can add support for different CI/CD services, for example ++ using @backstage-community/plugin-github-actions as follows: ++ ++ ++ ++ */} + +@@ -94,2 +110,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -110,2 +134,6 @@ const overviewContent = ( + ++ ++ ++ ++ + +@@ -129,2 +157,10 @@ const serviceEntityPage = ( + ++ ++ ++ ++ + +@@ -152,3 +188,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -167,2 +203,10 @@ const websiteEntityPage = ( + ++ ++ ++ ++ + +@@ -179,3 +223,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -198,3 +242,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -225,2 +269,5 @@ const apiPage = ( + ++ ++ ++ + +@@ -276,5 +323,8 @@ const groupPage = ( + +- ++ + + ++ ++ ++ + +@@ -292,3 +342,9 @@ const systemPage = ( + +- ++ ++ ++ ++ ++ ++ ++ + +@@ -304,3 +360,19 @@ const systemPage = ( + +- ++ + +@@ -317,2 +389,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/app/src/index.tsx b/packages/app/src/index.tsx +index b16aaf7..d875c77 100644 +--- a/packages/app/src/index.tsx ++++ b/packages/app/src/index.tsx +@@ -2,5 +2,5 @@ import '@backstage/cli/asset-types'; + import React from 'react'; +-import ReactDOM from 'react-dom'; ++import ReactDOM from 'react-dom/client'; + import App from './App'; + +-ReactDOM.render(, document.getElementById('root')); ++ReactDOM.createRoot(document.getElementById('root')!).render(); +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..bef67b5 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,15 @@ 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 ++ ++# This will include the examples, if you don't need these simply remove this line ++COPY --chown=node:node examples ./examples + + # 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..6a14abc 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,32 +6,43 @@ + "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", +- "dockerode": "^3.3.1", +- "express": "^4.17.1", +- "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "@backstage/backend-common": "^0.24.0", ++ "@backstage/backend-defaults": "^0.4.2", ++ "@backstage/backend-tasks": "^0.6.0", ++ "@backstage/config": "^1.2.0", ++ "@backstage/plugin-app-backend": "^0.3.72", ++ "@backstage/plugin-auth-backend": "^0.22.10", ++ "@backstage/plugin-auth-backend-module-github-provider": "^0.1.20", ++ "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.9", ++ "@backstage/plugin-auth-node": "^0.5.0", ++ "@backstage/plugin-catalog-backend": "^1.25.0", ++ "@backstage/plugin-catalog-backend-module-logs": "^0.0.2", ++ "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.21", ++ "@backstage/plugin-kubernetes-backend": "^0.18.4", ++ "@backstage/plugin-permission-backend": "^0.5.47", ++ "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.1.20", ++ "@backstage/plugin-permission-common": "^0.8.1", ++ "@backstage/plugin-permission-node": "^0.8.1", ++ "@backstage/plugin-proxy-backend": "^0.5.4", ++ "@backstage/plugin-scaffolder-backend": "^1.24.0", ++ "@backstage/plugin-search-backend": "^1.5.15", ++ "@backstage/plugin-search-backend-module-catalog": "^0.2.0", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.33", ++ "@backstage/plugin-search-backend-module-techdocs": "^0.2.0", ++ "@backstage/plugin-search-backend-node": "^1.3.0", ++ "@backstage/plugin-techdocs-backend": "^1.10.10", ++ "app": "link:../app", ++ "better-sqlite3": "^9.0.0", ++ "node-gyp": "^10.0.0", ++ "pg": "^8.11.3", + "winston": "^3.2.1" +@@ -39,6 +50,6 @@ + "devDependencies": { +- "@backstage/cli": "^0.10.0", +- "@types/dockerode": "^3.3.0", ++ "@backstage/cli": "^0.27.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.test.ts b/packages/backend/src/index.test.ts +deleted file mode 100644 +index 7814b8c..0000000 +--- a/packages/backend/src/index.test.ts ++++ /dev/null +@@ -1,8 +0,0 @@ +-import { PluginEnvironment } from './types'; +- +-describe('test', () => { +- it('unbreaks the test runner', () => { +- const unbreaker = {} as PluginEnvironment; +- expect(unbreaker).toBeTruthy(); +- }); +-}); +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 3f12122..f57d4c5 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -8,83 +8,47 @@ + +-import Router from 'express-promise-router'; +-import { +- createServiceBuilder, +- loadBackendConfig, +- getRootLogger, +- useHotMemoize, +- notFoundHandler, +- CacheManager, +- DatabaseManager, +- SingleHostDiscovery, +- UrlReaders, +- ServerTokenManager, +-} from '@backstage/backend-common'; +-import { Config } from '@backstage/config'; +-import app from './plugins/app'; +-import auth from './plugins/auth'; +-import catalog from './plugins/catalog'; +-import scaffolder from './plugins/scaffolder'; +-import proxy from './plugins/proxy'; +-import techdocs from './plugins/techdocs'; +-import search from './plugins/search'; +-import { PluginEnvironment } from './types'; ++import { createBackend } from '@backstage/backend-defaults'; + +-function makeCreateEnv(config: Config) { +- const root = getRootLogger(); +- const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++const backend = createBackend(); + +- root.info(`Created UrlReader ${reader}`); ++backend.add(import('@backstage/plugin-app-backend/alpha')); ++backend.add(import('@backstage/plugin-proxy-backend/alpha')); ++backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); ++backend.add(import('@backstage/plugin-techdocs-backend/alpha')); + +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); +- const tokenManager = ServerTokenManager.noop(); ++// auth plugin ++backend.add(import('@backstage/plugin-auth-backend')); ++// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin ++backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); ++// See https://backstage.io/docs/auth/guest/provider + +- return (plugin: string): PluginEnvironment => { +- const logger = root.child({ type: 'plugin', plugin }); +- const database = databaseManager.forPlugin(plugin); +- const cache = cacheManager.forPlugin(plugin); +- return { logger, database, cache, config, reader, discovery, tokenManager }; +- }; +-} ++// catalog plugin ++backend.add(import('@backstage/plugin-catalog-backend/alpha')); ++backend.add( ++ import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ++); + +-async function main() { +- const config = await loadBackendConfig({ +- argv: process.argv, +- logger: getRootLogger(), +- }); +- const createEnv = makeCreateEnv(config); ++// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors ++backend.add(import('@backstage/plugin-catalog-backend-module-logs')); + +- const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); +- const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); +- const authEnv = useHotMemoize(module, () => createEnv('auth')); +- const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); +- const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); +- const searchEnv = useHotMemoize(module, () => createEnv('search')); +- const appEnv = useHotMemoize(module, () => createEnv('app')); ++// permission plugin ++backend.add(import('@backstage/plugin-permission-backend/alpha')); ++// See https://backstage.io/docs/permissions/getting-started for how to create your own permission policy ++backend.add( ++ import('@backstage/plugin-permission-backend-module-allow-all-policy'), ++); + +- const apiRouter = Router(); +- apiRouter.use('/catalog', await catalog(catalogEnv)); +- apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); +- apiRouter.use('/auth', await auth(authEnv)); +- apiRouter.use('/techdocs', await techdocs(techdocsEnv)); +- apiRouter.use('/proxy', await proxy(proxyEnv)); +- apiRouter.use('/search', await search(searchEnv)); +- apiRouter.use(notFoundHandler()); ++// search plugin ++backend.add(import('@backstage/plugin-search-backend/alpha')); + +- const service = createServiceBuilder(module) +- .loadConfig(config) +- .addRouter('/api', apiRouter) +- .addRouter('', await app(appEnv)); ++// search engine ++// See https://backstage.io/docs/features/search/search-engines ++backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); + +- await service.start().catch(err => { +- console.log(err); +- process.exit(1); +- }); +-} ++// search collators ++backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); ++backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); + +-module.hot?.accept(); +-main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); +- process.exit(1); +-}); ++// kubernetes ++backend.add(import('@backstage/plugin-kubernetes-backend/alpha')); ++ ++backend.start(); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +deleted file mode 100644 +index 07fb04f..0000000 +--- a/packages/backend/src/plugins/app.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { createRouter } from '@backstage/plugin-app-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +-}: PluginEnvironment): Promise { +- return await createRouter({ +- logger, +- config, +- appPackageName: 'app', +- }); +-} +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +deleted file mode 100644 +index 5216510..0000000 +--- a/packages/backend/src/plugins/auth.ts ++++ /dev/null +@@ -1,12 +0,0 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, database, discovery }); +-} +diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts +deleted file mode 100644 +index 876cb6b..0000000 +--- a/packages/backend/src/plugins/catalog.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +-import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const builder = await CatalogBuilder.create(env); +- builder.addProcessor(new ScaffolderEntitiesProcessor()); +- const { processingEngine, router } = await builder.build(); +- await processingEngine.start(); +- return router; +-} +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +deleted file mode 100644 +index 506f6d9..0000000 +--- a/packages/backend/src/plugins/proxy.ts ++++ /dev/null +@@ -1,11 +0,0 @@ +-import { createRouter } from '@backstage/plugin-proxy-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); +-} +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +deleted file mode 100644 +index 6be2e97..0000000 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ /dev/null +@@ -1,27 +0,0 @@ +-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'; +-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 }); +- +- return await createRouter({ +- containerRunner, +- logger, +- config, +- database, +- catalogClient, +- reader, +- }); +-} +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +deleted file mode 100644 +index f23b0c7..0000000 +--- a/packages/backend/src/plugins/search.ts ++++ /dev/null +@@ -1,54 +0,0 @@ +-import { useHotCleanup } from '@backstage/backend-common'; +-import { createRouter } from '@backstage/plugin-search-backend'; +-import { +- IndexBuilder, +- LunrSearchEngine, +-} from '@backstage/plugin-search-backend-node'; +-import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend'; +- +-export default async function createPlugin({ +- logger, +- discovery, +- config, +- tokenManager, +-}: PluginEnvironment) { +- // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); +- +- // Collators are responsible for gathering documents known to plugins. This +- // collator gathers entities from the software catalog. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { +- discovery, +- tokenManager, +- }), +- }); +- +- // collator gathers entities from techdocs. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultTechDocsCollator.fromConfig(config, { +- discovery, +- logger, +- tokenManager, +- }), +- }); +- +- // The scheduler controls when documents are gathered from collators and sent +- // to the search engine for indexing. +- const { scheduler } = await indexBuilder.build(); +- +- // 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()); +- +- return await createRouter({ +- engine: indexBuilder.getSearchEngine(), +- logger, +- }); +-} +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +deleted file mode 100644 +index 906d86d..0000000 +--- a/packages/backend/src/plugins/techdocs.ts ++++ /dev/null +@@ -1,53 +0,0 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; +-import { +- createRouter, +- Generators, +- Preparers, +- Publisher, +-} from '@backstage/plugin-techdocs-backend'; +-import Docker from 'dockerode'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +-}: PluginEnvironment): Promise { +- // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, +- }); +- +- // Docker client (conditionally) used by the generators, based on techdocs.generators config. +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- +- // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, +- containerRunner, +- }); +- +- // Publisher is used for +- // 1. Publishing generated files to storage +- // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, +- }); +- +- // checks if the publisher is working and logs the result +- await publisher.getReadiness(); +- +- return await createRouter({ +- preparers, +- generators, +- publisher, +- logger, +- config, +- discovery, +- }); +-} +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +deleted file mode 100644 +index b1e2e0a..0000000 +--- a/packages/backend/src/types.ts ++++ /dev/null +@@ -1,19 +0,0 @@ +-import { Logger } from 'winston'; +-import { Config } from '@backstage/config'; +-import { +- PluginCacheManager, +- PluginDatabaseManager, +- PluginEndpointDiscovery, +- TokenManager, +- UrlReader, +-} from '@backstage/backend-common'; +- +-export type PluginEnvironment = { +- logger: Logger; +- database: PluginDatabaseManager; +- cache: PluginCacheManager; +- config: Config; +- reader: UrlReader; +- discovery: PluginEndpointDiscovery; +- tokenManager: TokenManager; +-}; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..733be13 +--- /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 dev', ++ 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..c37fc61 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1,3 @@ ++# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. ++# yarn lockfile v1 ++ diff --git a/diffs/0.4.7..0.5.18.diff b/diffs/0.4.7..0.5.18.diff new file mode 100644 index 00000000..97ba0690 --- /dev/null +++ b/diffs/0.4.7..0.5.18.diff @@ -0,0 +1,2097 @@ +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..5d426f5 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,51 @@ 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/apis/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 ++ ++auth: ++ providers: ++ guest: {} ++ ++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: ++ # Local example data, replace this with your production config, these are intended for demo use only. ++ # File locations are relative to the backend process, typically in a deployed context, such as in a Docker container, this will be the root ++ - type: file ++ target: ./examples/entities.yaml ++ ++ # Local example template ++ - type: file ++ target: ./examples/template/template.yaml ++ rules: ++ - allow: [Template] ++ ++ # Local example organizational data ++ - type: file ++ target: ./examples/org.yaml ++ rules: ++ - allow: [User, Group] +diff --git a/app-config.yaml b/app-config.yaml +index 1a622a2..ca52ec5 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/integrations/github/locations#configuration + 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 + +@@ -58,3 +66,5 @@ auth: + # see https://backstage.io/docs/auth/ to learn about auth providers +- providers: {} ++ providers: ++ # See https://backstage.io/docs/auth/guest/provider ++ guest: {} + +@@ -64,41 +74,40 @@ 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 ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml ++ rules: ++ - allow: [Template] + +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml ++ rules: ++ - allow: [User, Group] + +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml + +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.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] + +- # 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 +- 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 +- rules: +- - allow: [Template] ++kubernetes: ++ # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options ++ ++# see https://backstage.io/docs/permissions/getting-started for more on the permission framework ++permission: ++ # setting this to `false` will disable permissions ++ enabled: true +diff --git a/backstage.json b/backstage.json +index 10f65a1..230d39d 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "0.4.7" ++ "version": "1.30.0" + } +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..80a963e 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.27.0", ++ "@backstage/e2e-test-utils": "^0.1.1", ++ "@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": "^10.0.0", ++ "prettier": "^2.3.2", ++ "typescript": "~5.4.0" ++ }, ++ "resolutions": { ++ "@types/react": "^18", ++ "@types/react-dom": "^18" + }, +@@ -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..839ff88 +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,27 @@ ++/* ++ * 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('/'); ++ ++ const enterButton = page.getByRole('button', { name: 'Enter' }); ++ await expect(enterButton).toBeVisible(); ++ await enterButton.click(); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 3243e81..346ffc1 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,22 +5,37 @@ + "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.5.10", ++ "@backstage/catalog-model": "^1.6.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/core-app-api": "^1.14.2", ++ "@backstage/core-components": "^0.14.10", ++ "@backstage/core-plugin-api": "^1.9.3", ++ "@backstage/integration-react": "^1.1.30", ++ "@backstage/plugin-api-docs": "^0.11.8", ++ "@backstage/plugin-catalog": "^1.22.0", ++ "@backstage/plugin-catalog-common": "^1.0.26", ++ "@backstage/plugin-catalog-graph": "^0.4.8", ++ "@backstage/plugin-catalog-import": "^0.12.2", ++ "@backstage/plugin-catalog-react": "^1.12.3", ++ "@backstage/plugin-kubernetes": "^0.11.13", ++ "@backstage/plugin-org": "^0.6.28", ++ "@backstage/plugin-permission-react": "^0.4.25", ++ "@backstage/plugin-scaffolder": "^1.24.0", ++ "@backstage/plugin-search": "^1.4.15", ++ "@backstage/plugin-search-react": "^1.7.14", ++ "@backstage/plugin-techdocs": "^1.10.8", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.13", ++ "@backstage/plugin-techdocs-react": "^1.2.7", ++ "@backstage/plugin-user-settings": "^0.8.11", ++ "@backstage/theme": "^0.5.6", + "@material-ui/core": "^4.12.2", +@@ -28,31 +43,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": "^18.0.2", ++ "react-dom": "^18.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", +- "@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", ++ "@backstage/test-utils": "^1.5.10", ++ "@playwright/test": "^1.32.3", ++ "@testing-library/jest-dom": "^6.0.0", ++ "@testing-library/react": "^14.0.0", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^9.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..18da7c4 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -8,5 +8,4 @@ + name="description" +- content="Backstage is an open platform for building developer portals" ++ content="Backstage is an open source framework for building developer portals" + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index b94cac7..ec8ba1d 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { renderWithEffects } from '@backstage/test-utils'; ++import { render, waitFor } from '@testing-library/react'; + import App from './App'; +@@ -22,4 +22,7 @@ describe('App', () => { + +- const rendered = await renderWithEffects(); +- expect(rendered.baseElement).toBeInTheDocument(); ++ const rendered = render(); ++ ++ await waitFor(() => { ++ expect(rendered.baseElement).toBeInTheDocument(); ++ }); + }); +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 8a53583..e24c468 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,6 +13,5 @@ import { + import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; ++import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +-import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -21,2 +20,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'; +@@ -27,5 +28,12 @@ import { Root } from './components/Root'; + +-import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; ++import { ++ AlertDisplay, ++ OAuthRequestDialog, ++ SignInPage, ++} 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 +45,6 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); + bind(apiDocsPlugin.externalRoutes, { +- createComponent: scaffolderPlugin.routes.root, ++ registerApi: catalogImportPlugin.routes.importPage, + }); +@@ -43,3 +52,10 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); ++ bind(orgPlugin.externalRoutes, { ++ catalogIndex: catalogPlugin.routes.catalogIndex, ++ }); ++ }, ++ components: { ++ SignInPage: props => , + }, +@@ -47,8 +63,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -60,5 +73,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -70,6 +85,9 @@ const routes = ( + } ++ path="/catalog-import" ++ element={ ++ ++ ++ ++ } + /> +- } /> + }> +@@ -78,2 +96,3 @@ const routes = ( + } /> ++ } /> + +@@ -81,4 +100,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -88,5 +107,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..853d175 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,22 +1,5 @@ +-/* +- * 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'; + import ExtensionIcon from '@material-ui/icons/Extension'; +-import MapIcon from '@material-ui/icons/MyLocation'; + import LibraryBooks from '@material-ui/icons/LibraryBooks'; +@@ -25,18 +8,23 @@ 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'; ++import { MyGroupsSidebarItem } from '@backstage/plugin-org'; ++import GroupIcon from '@material-ui/icons/People'; + +@@ -59,3 +47,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -63,8 +51,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -79,19 +62,32 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ } to="/search"> + +- ++ + +- {/* Global nav, not org-specific */} +- +- +- +- +- {/* End global nav */} +- +- +- +- ++ }> ++ {/* Global nav, not org-specific */} ++ ++ ++ ++ ++ ++ {/* End global nav */} ++ ++ ++ {/* Items in this group will be scrollable if they run out of space */} ++ ++ + + +- ++ } ++ 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..6164c76 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,7 +27,5 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +-import { +- isGithubActionsAvailable, +- EntityGithubActionsContent, +-} from '@backstage/plugin-github-actions'; + import { +@@ -56,2 +38,32 @@ 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'; ++ ++import { ++ EntityKubernetesContent, ++ isKubernetesAvailable, ++} from '@backstage/plugin-kubernetes'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); + +@@ -61,5 +73,9 @@ const cicdContent = ( + +- +- +- ++ {/* ++ Here you can add support for different CI/CD services, for example ++ using @backstage-community/plugin-github-actions as follows: ++ ++ ++ ++ */} + +@@ -94,2 +110,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -110,2 +134,6 @@ const overviewContent = ( + ++ ++ ++ ++ + +@@ -129,2 +157,10 @@ const serviceEntityPage = ( + ++ ++ ++ ++ + +@@ -152,3 +188,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -167,2 +203,10 @@ const websiteEntityPage = ( + ++ ++ ++ ++ + +@@ -179,3 +223,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -198,3 +242,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -225,2 +269,5 @@ const apiPage = ( + ++ ++ ++ + +@@ -276,5 +323,8 @@ const groupPage = ( + +- ++ + + ++ ++ ++ + +@@ -292,3 +342,9 @@ const systemPage = ( + +- ++ ++ ++ ++ ++ ++ ++ + +@@ -304,3 +360,19 @@ const systemPage = ( + +- ++ + +@@ -317,2 +389,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/app/src/index.tsx b/packages/app/src/index.tsx +index b16aaf7..d875c77 100644 +--- a/packages/app/src/index.tsx ++++ b/packages/app/src/index.tsx +@@ -2,5 +2,5 @@ import '@backstage/cli/asset-types'; + import React from 'react'; +-import ReactDOM from 'react-dom'; ++import ReactDOM from 'react-dom/client'; + import App from './App'; + +-ReactDOM.render(, document.getElementById('root')); ++ReactDOM.createRoot(document.getElementById('root')!).render(); +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..bef67b5 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,15 @@ 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 ++ ++# This will include the examples, if you don't need these simply remove this line ++COPY --chown=node:node examples ./examples + + # 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..6a14abc 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,32 +6,43 @@ + "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", +- "dockerode": "^3.3.1", +- "express": "^4.17.1", +- "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "@backstage/backend-common": "^0.24.0", ++ "@backstage/backend-defaults": "^0.4.2", ++ "@backstage/backend-tasks": "^0.6.0", ++ "@backstage/config": "^1.2.0", ++ "@backstage/plugin-app-backend": "^0.3.72", ++ "@backstage/plugin-auth-backend": "^0.22.10", ++ "@backstage/plugin-auth-backend-module-github-provider": "^0.1.20", ++ "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.9", ++ "@backstage/plugin-auth-node": "^0.5.0", ++ "@backstage/plugin-catalog-backend": "^1.25.0", ++ "@backstage/plugin-catalog-backend-module-logs": "^0.0.2", ++ "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.21", ++ "@backstage/plugin-kubernetes-backend": "^0.18.4", ++ "@backstage/plugin-permission-backend": "^0.5.47", ++ "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.1.20", ++ "@backstage/plugin-permission-common": "^0.8.1", ++ "@backstage/plugin-permission-node": "^0.8.1", ++ "@backstage/plugin-proxy-backend": "^0.5.4", ++ "@backstage/plugin-scaffolder-backend": "^1.24.0", ++ "@backstage/plugin-search-backend": "^1.5.15", ++ "@backstage/plugin-search-backend-module-catalog": "^0.2.0", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.33", ++ "@backstage/plugin-search-backend-module-techdocs": "^0.2.0", ++ "@backstage/plugin-search-backend-node": "^1.3.0", ++ "@backstage/plugin-techdocs-backend": "^1.10.10", ++ "app": "link:../app", ++ "better-sqlite3": "^9.0.0", ++ "node-gyp": "^10.0.0", ++ "pg": "^8.11.3", + "winston": "^3.2.1" +@@ -39,6 +50,6 @@ + "devDependencies": { +- "@backstage/cli": "^0.10.1", +- "@types/dockerode": "^3.3.0", ++ "@backstage/cli": "^0.27.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.test.ts b/packages/backend/src/index.test.ts +deleted file mode 100644 +index 7814b8c..0000000 +--- a/packages/backend/src/index.test.ts ++++ /dev/null +@@ -1,8 +0,0 @@ +-import { PluginEnvironment } from './types'; +- +-describe('test', () => { +- it('unbreaks the test runner', () => { +- const unbreaker = {} as PluginEnvironment; +- expect(unbreaker).toBeTruthy(); +- }); +-}); +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 3f12122..f57d4c5 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -8,83 +8,47 @@ + +-import Router from 'express-promise-router'; +-import { +- createServiceBuilder, +- loadBackendConfig, +- getRootLogger, +- useHotMemoize, +- notFoundHandler, +- CacheManager, +- DatabaseManager, +- SingleHostDiscovery, +- UrlReaders, +- ServerTokenManager, +-} from '@backstage/backend-common'; +-import { Config } from '@backstage/config'; +-import app from './plugins/app'; +-import auth from './plugins/auth'; +-import catalog from './plugins/catalog'; +-import scaffolder from './plugins/scaffolder'; +-import proxy from './plugins/proxy'; +-import techdocs from './plugins/techdocs'; +-import search from './plugins/search'; +-import { PluginEnvironment } from './types'; ++import { createBackend } from '@backstage/backend-defaults'; + +-function makeCreateEnv(config: Config) { +- const root = getRootLogger(); +- const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++const backend = createBackend(); + +- root.info(`Created UrlReader ${reader}`); ++backend.add(import('@backstage/plugin-app-backend/alpha')); ++backend.add(import('@backstage/plugin-proxy-backend/alpha')); ++backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); ++backend.add(import('@backstage/plugin-techdocs-backend/alpha')); + +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); +- const tokenManager = ServerTokenManager.noop(); ++// auth plugin ++backend.add(import('@backstage/plugin-auth-backend')); ++// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin ++backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); ++// See https://backstage.io/docs/auth/guest/provider + +- return (plugin: string): PluginEnvironment => { +- const logger = root.child({ type: 'plugin', plugin }); +- const database = databaseManager.forPlugin(plugin); +- const cache = cacheManager.forPlugin(plugin); +- return { logger, database, cache, config, reader, discovery, tokenManager }; +- }; +-} ++// catalog plugin ++backend.add(import('@backstage/plugin-catalog-backend/alpha')); ++backend.add( ++ import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ++); + +-async function main() { +- const config = await loadBackendConfig({ +- argv: process.argv, +- logger: getRootLogger(), +- }); +- const createEnv = makeCreateEnv(config); ++// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors ++backend.add(import('@backstage/plugin-catalog-backend-module-logs')); + +- const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); +- const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); +- const authEnv = useHotMemoize(module, () => createEnv('auth')); +- const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); +- const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); +- const searchEnv = useHotMemoize(module, () => createEnv('search')); +- const appEnv = useHotMemoize(module, () => createEnv('app')); ++// permission plugin ++backend.add(import('@backstage/plugin-permission-backend/alpha')); ++// See https://backstage.io/docs/permissions/getting-started for how to create your own permission policy ++backend.add( ++ import('@backstage/plugin-permission-backend-module-allow-all-policy'), ++); + +- const apiRouter = Router(); +- apiRouter.use('/catalog', await catalog(catalogEnv)); +- apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); +- apiRouter.use('/auth', await auth(authEnv)); +- apiRouter.use('/techdocs', await techdocs(techdocsEnv)); +- apiRouter.use('/proxy', await proxy(proxyEnv)); +- apiRouter.use('/search', await search(searchEnv)); +- apiRouter.use(notFoundHandler()); ++// search plugin ++backend.add(import('@backstage/plugin-search-backend/alpha')); + +- const service = createServiceBuilder(module) +- .loadConfig(config) +- .addRouter('/api', apiRouter) +- .addRouter('', await app(appEnv)); ++// search engine ++// See https://backstage.io/docs/features/search/search-engines ++backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); + +- await service.start().catch(err => { +- console.log(err); +- process.exit(1); +- }); +-} ++// search collators ++backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); ++backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); + +-module.hot?.accept(); +-main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); +- process.exit(1); +-}); ++// kubernetes ++backend.add(import('@backstage/plugin-kubernetes-backend/alpha')); ++ ++backend.start(); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +deleted file mode 100644 +index 07fb04f..0000000 +--- a/packages/backend/src/plugins/app.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { createRouter } from '@backstage/plugin-app-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +-}: PluginEnvironment): Promise { +- return await createRouter({ +- logger, +- config, +- appPackageName: 'app', +- }); +-} +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +deleted file mode 100644 +index 5216510..0000000 +--- a/packages/backend/src/plugins/auth.ts ++++ /dev/null +@@ -1,12 +0,0 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, database, discovery }); +-} +diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts +deleted file mode 100644 +index 876cb6b..0000000 +--- a/packages/backend/src/plugins/catalog.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +-import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const builder = await CatalogBuilder.create(env); +- builder.addProcessor(new ScaffolderEntitiesProcessor()); +- const { processingEngine, router } = await builder.build(); +- await processingEngine.start(); +- return router; +-} +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +deleted file mode 100644 +index 506f6d9..0000000 +--- a/packages/backend/src/plugins/proxy.ts ++++ /dev/null +@@ -1,11 +0,0 @@ +-import { createRouter } from '@backstage/plugin-proxy-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); +-} +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +deleted file mode 100644 +index 6be2e97..0000000 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ /dev/null +@@ -1,27 +0,0 @@ +-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'; +-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 }); +- +- return await createRouter({ +- containerRunner, +- logger, +- config, +- database, +- catalogClient, +- reader, +- }); +-} +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +deleted file mode 100644 +index f23b0c7..0000000 +--- a/packages/backend/src/plugins/search.ts ++++ /dev/null +@@ -1,54 +0,0 @@ +-import { useHotCleanup } from '@backstage/backend-common'; +-import { createRouter } from '@backstage/plugin-search-backend'; +-import { +- IndexBuilder, +- LunrSearchEngine, +-} from '@backstage/plugin-search-backend-node'; +-import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend'; +- +-export default async function createPlugin({ +- logger, +- discovery, +- config, +- tokenManager, +-}: PluginEnvironment) { +- // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); +- +- // Collators are responsible for gathering documents known to plugins. This +- // collator gathers entities from the software catalog. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { +- discovery, +- tokenManager, +- }), +- }); +- +- // collator gathers entities from techdocs. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultTechDocsCollator.fromConfig(config, { +- discovery, +- logger, +- tokenManager, +- }), +- }); +- +- // The scheduler controls when documents are gathered from collators and sent +- // to the search engine for indexing. +- const { scheduler } = await indexBuilder.build(); +- +- // 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()); +- +- return await createRouter({ +- engine: indexBuilder.getSearchEngine(), +- logger, +- }); +-} +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +deleted file mode 100644 +index 054c64d..0000000 +--- a/packages/backend/src/plugins/techdocs.ts ++++ /dev/null +@@ -1,55 +0,0 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; +-import { +- createRouter, +- Generators, +- Preparers, +- Publisher, +-} from '@backstage/plugin-techdocs-backend'; +-import Docker from 'dockerode'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +- cache, +-}: PluginEnvironment): Promise { +- // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, +- }); +- +- // Docker client (conditionally) used by the generators, based on techdocs.generators config. +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- +- // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, +- containerRunner, +- }); +- +- // Publisher is used for +- // 1. Publishing generated files to storage +- // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, +- }); +- +- // checks if the publisher is working and logs the result +- await publisher.getReadiness(); +- +- return await createRouter({ +- preparers, +- generators, +- publisher, +- logger, +- config, +- discovery, +- cache, +- }); +-} +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +deleted file mode 100644 +index b1e2e0a..0000000 +--- a/packages/backend/src/types.ts ++++ /dev/null +@@ -1,19 +0,0 @@ +-import { Logger } from 'winston'; +-import { Config } from '@backstage/config'; +-import { +- PluginCacheManager, +- PluginDatabaseManager, +- PluginEndpointDiscovery, +- TokenManager, +- UrlReader, +-} from '@backstage/backend-common'; +- +-export type PluginEnvironment = { +- logger: Logger; +- database: PluginDatabaseManager; +- cache: PluginCacheManager; +- config: Config; +- reader: UrlReader; +- discovery: PluginEndpointDiscovery; +- tokenManager: TokenManager; +-}; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..733be13 +--- /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 dev', ++ 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..c37fc61 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1,3 @@ ++# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. ++# yarn lockfile v1 ++ diff --git a/diffs/0.4.8..0.5.18.diff b/diffs/0.4.8..0.5.18.diff new file mode 100644 index 00000000..afb3b1d8 --- /dev/null +++ b/diffs/0.4.8..0.5.18.diff @@ -0,0 +1,2092 @@ +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..5d426f5 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,51 @@ 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/apis/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 ++ ++auth: ++ providers: ++ guest: {} ++ ++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: ++ # Local example data, replace this with your production config, these are intended for demo use only. ++ # File locations are relative to the backend process, typically in a deployed context, such as in a Docker container, this will be the root ++ - type: file ++ target: ./examples/entities.yaml ++ ++ # Local example template ++ - type: file ++ target: ./examples/template/template.yaml ++ rules: ++ - allow: [Template] ++ ++ # Local example organizational data ++ - type: file ++ target: ./examples/org.yaml ++ rules: ++ - allow: [User, Group] +diff --git a/app-config.yaml b/app-config.yaml +index 1a622a2..ca52ec5 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/integrations/github/locations#configuration + 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 + +@@ -58,3 +66,5 @@ auth: + # see https://backstage.io/docs/auth/ to learn about auth providers +- providers: {} ++ providers: ++ # See https://backstage.io/docs/auth/guest/provider ++ guest: {} + +@@ -64,41 +74,40 @@ 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 ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml ++ rules: ++ - allow: [Template] + +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml ++ rules: ++ - allow: [User, Group] + +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml + +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.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] + +- # 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 +- 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 +- rules: +- - allow: [Template] ++kubernetes: ++ # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options ++ ++# see https://backstage.io/docs/permissions/getting-started for more on the permission framework ++permission: ++ # setting this to `false` will disable permissions ++ enabled: true +diff --git a/backstage.json b/backstage.json +index 87a2209..230d39d 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "0.4.8" ++ "version": "1.30.0" + } +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..80a963e 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.27.0", ++ "@backstage/e2e-test-utils": "^0.1.1", ++ "@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": "^10.0.0", ++ "prettier": "^2.3.2", ++ "typescript": "~5.4.0" ++ }, ++ "resolutions": { ++ "@types/react": "^18", ++ "@types/react-dom": "^18" + }, +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..839ff88 +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,27 @@ ++/* ++ * 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('/'); ++ ++ const enterButton = page.getByRole('button', { name: 'Enter' }); ++ await expect(enterButton).toBeVisible(); ++ await enterButton.click(); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 948a4c5..346ffc1 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,22 +5,37 @@ + "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.5.10", ++ "@backstage/catalog-model": "^1.6.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/core-app-api": "^1.14.2", ++ "@backstage/core-components": "^0.14.10", ++ "@backstage/core-plugin-api": "^1.9.3", ++ "@backstage/integration-react": "^1.1.30", ++ "@backstage/plugin-api-docs": "^0.11.8", ++ "@backstage/plugin-catalog": "^1.22.0", ++ "@backstage/plugin-catalog-common": "^1.0.26", ++ "@backstage/plugin-catalog-graph": "^0.4.8", ++ "@backstage/plugin-catalog-import": "^0.12.2", ++ "@backstage/plugin-catalog-react": "^1.12.3", ++ "@backstage/plugin-kubernetes": "^0.11.13", ++ "@backstage/plugin-org": "^0.6.28", ++ "@backstage/plugin-permission-react": "^0.4.25", ++ "@backstage/plugin-scaffolder": "^1.24.0", ++ "@backstage/plugin-search": "^1.4.15", ++ "@backstage/plugin-search-react": "^1.7.14", ++ "@backstage/plugin-techdocs": "^1.10.8", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.13", ++ "@backstage/plugin-techdocs-react": "^1.2.7", ++ "@backstage/plugin-user-settings": "^0.8.11", ++ "@backstage/theme": "^0.5.6", + "@material-ui/core": "^4.12.2", +@@ -28,31 +43,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": "^18.0.2", ++ "react-dom": "^18.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", +- "@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", ++ "@backstage/test-utils": "^1.5.10", ++ "@playwright/test": "^1.32.3", ++ "@testing-library/jest-dom": "^6.0.0", ++ "@testing-library/react": "^14.0.0", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^9.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..18da7c4 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -8,5 +8,4 @@ + name="description" +- content="Backstage is an open platform for building developer portals" ++ content="Backstage is an open source framework for building developer portals" + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index b94cac7..ec8ba1d 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { renderWithEffects } from '@backstage/test-utils'; ++import { render, waitFor } from '@testing-library/react'; + import App from './App'; +@@ -22,4 +22,7 @@ describe('App', () => { + +- const rendered = await renderWithEffects(); +- expect(rendered.baseElement).toBeInTheDocument(); ++ const rendered = render(); ++ ++ await waitFor(() => { ++ expect(rendered.baseElement).toBeInTheDocument(); ++ }); + }); +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 8a53583..e24c468 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,6 +13,5 @@ import { + import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; ++import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +-import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -21,2 +20,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'; +@@ -27,5 +28,12 @@ import { Root } from './components/Root'; + +-import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; ++import { ++ AlertDisplay, ++ OAuthRequestDialog, ++ SignInPage, ++} 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 +45,6 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); + bind(apiDocsPlugin.externalRoutes, { +- createComponent: scaffolderPlugin.routes.root, ++ registerApi: catalogImportPlugin.routes.importPage, + }); +@@ -43,3 +52,10 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); ++ bind(orgPlugin.externalRoutes, { ++ catalogIndex: catalogPlugin.routes.catalogIndex, ++ }); ++ }, ++ components: { ++ SignInPage: props => , + }, +@@ -47,8 +63,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -60,5 +73,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -70,6 +85,9 @@ const routes = ( + } ++ path="/catalog-import" ++ element={ ++ ++ ++ ++ } + /> +- } /> + }> +@@ -78,2 +96,3 @@ const routes = ( + } /> ++ } /> + +@@ -81,4 +100,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -88,5 +107,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..853d175 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,22 +1,5 @@ +-/* +- * 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'; + import ExtensionIcon from '@material-ui/icons/Extension'; +-import MapIcon from '@material-ui/icons/MyLocation'; + import LibraryBooks from '@material-ui/icons/LibraryBooks'; +@@ -25,18 +8,23 @@ 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'; ++import { MyGroupsSidebarItem } from '@backstage/plugin-org'; ++import GroupIcon from '@material-ui/icons/People'; + +@@ -59,3 +47,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -63,8 +51,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -79,19 +62,32 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ } to="/search"> + +- ++ + +- {/* Global nav, not org-specific */} +- +- +- +- +- {/* End global nav */} +- +- +- +- ++ }> ++ {/* Global nav, not org-specific */} ++ ++ ++ ++ ++ ++ {/* End global nav */} ++ ++ ++ {/* Items in this group will be scrollable if they run out of space */} ++ ++ + + +- ++ } ++ 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..6164c76 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,7 +27,5 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +-import { +- isGithubActionsAvailable, +- EntityGithubActionsContent, +-} from '@backstage/plugin-github-actions'; + import { +@@ -56,2 +38,32 @@ 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'; ++ ++import { ++ EntityKubernetesContent, ++ isKubernetesAvailable, ++} from '@backstage/plugin-kubernetes'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); + +@@ -61,5 +73,9 @@ const cicdContent = ( + +- +- +- ++ {/* ++ Here you can add support for different CI/CD services, for example ++ using @backstage-community/plugin-github-actions as follows: ++ ++ ++ ++ */} + +@@ -94,2 +110,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -110,2 +134,6 @@ const overviewContent = ( + ++ ++ ++ ++ + +@@ -129,2 +157,10 @@ const serviceEntityPage = ( + ++ ++ ++ ++ + +@@ -152,3 +188,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -167,2 +203,10 @@ const websiteEntityPage = ( + ++ ++ ++ ++ + +@@ -179,3 +223,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -198,3 +242,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -225,2 +269,5 @@ const apiPage = ( + ++ ++ ++ + +@@ -276,5 +323,8 @@ const groupPage = ( + +- ++ + + ++ ++ ++ + +@@ -292,3 +342,9 @@ const systemPage = ( + +- ++ ++ ++ ++ ++ ++ ++ + +@@ -304,3 +360,19 @@ const systemPage = ( + +- ++ + +@@ -317,2 +389,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/app/src/index.tsx b/packages/app/src/index.tsx +index b16aaf7..d875c77 100644 +--- a/packages/app/src/index.tsx ++++ b/packages/app/src/index.tsx +@@ -2,5 +2,5 @@ import '@backstage/cli/asset-types'; + import React from 'react'; +-import ReactDOM from 'react-dom'; ++import ReactDOM from 'react-dom/client'; + import App from './App'; + +-ReactDOM.render(, document.getElementById('root')); ++ReactDOM.createRoot(document.getElementById('root')!).render(); +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..bef67b5 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,15 @@ 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 ++ ++# This will include the examples, if you don't need these simply remove this line ++COPY --chown=node:node examples ./examples + + # 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..6a14abc 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,32 +6,43 @@ + "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", +- "dockerode": "^3.3.1", +- "express": "^4.17.1", +- "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "@backstage/backend-common": "^0.24.0", ++ "@backstage/backend-defaults": "^0.4.2", ++ "@backstage/backend-tasks": "^0.6.0", ++ "@backstage/config": "^1.2.0", ++ "@backstage/plugin-app-backend": "^0.3.72", ++ "@backstage/plugin-auth-backend": "^0.22.10", ++ "@backstage/plugin-auth-backend-module-github-provider": "^0.1.20", ++ "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.9", ++ "@backstage/plugin-auth-node": "^0.5.0", ++ "@backstage/plugin-catalog-backend": "^1.25.0", ++ "@backstage/plugin-catalog-backend-module-logs": "^0.0.2", ++ "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.21", ++ "@backstage/plugin-kubernetes-backend": "^0.18.4", ++ "@backstage/plugin-permission-backend": "^0.5.47", ++ "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.1.20", ++ "@backstage/plugin-permission-common": "^0.8.1", ++ "@backstage/plugin-permission-node": "^0.8.1", ++ "@backstage/plugin-proxy-backend": "^0.5.4", ++ "@backstage/plugin-scaffolder-backend": "^1.24.0", ++ "@backstage/plugin-search-backend": "^1.5.15", ++ "@backstage/plugin-search-backend-module-catalog": "^0.2.0", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.33", ++ "@backstage/plugin-search-backend-module-techdocs": "^0.2.0", ++ "@backstage/plugin-search-backend-node": "^1.3.0", ++ "@backstage/plugin-techdocs-backend": "^1.10.10", ++ "app": "link:../app", ++ "better-sqlite3": "^9.0.0", ++ "node-gyp": "^10.0.0", ++ "pg": "^8.11.3", + "winston": "^3.2.1" +@@ -39,6 +50,6 @@ + "devDependencies": { +- "@backstage/cli": "^0.10.2", +- "@types/dockerode": "^3.3.0", ++ "@backstage/cli": "^0.27.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.test.ts b/packages/backend/src/index.test.ts +deleted file mode 100644 +index 7814b8c..0000000 +--- a/packages/backend/src/index.test.ts ++++ /dev/null +@@ -1,8 +0,0 @@ +-import { PluginEnvironment } from './types'; +- +-describe('test', () => { +- it('unbreaks the test runner', () => { +- const unbreaker = {} as PluginEnvironment; +- expect(unbreaker).toBeTruthy(); +- }); +-}); +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 3f12122..f57d4c5 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -8,83 +8,47 @@ + +-import Router from 'express-promise-router'; +-import { +- createServiceBuilder, +- loadBackendConfig, +- getRootLogger, +- useHotMemoize, +- notFoundHandler, +- CacheManager, +- DatabaseManager, +- SingleHostDiscovery, +- UrlReaders, +- ServerTokenManager, +-} from '@backstage/backend-common'; +-import { Config } from '@backstage/config'; +-import app from './plugins/app'; +-import auth from './plugins/auth'; +-import catalog from './plugins/catalog'; +-import scaffolder from './plugins/scaffolder'; +-import proxy from './plugins/proxy'; +-import techdocs from './plugins/techdocs'; +-import search from './plugins/search'; +-import { PluginEnvironment } from './types'; ++import { createBackend } from '@backstage/backend-defaults'; + +-function makeCreateEnv(config: Config) { +- const root = getRootLogger(); +- const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++const backend = createBackend(); + +- root.info(`Created UrlReader ${reader}`); ++backend.add(import('@backstage/plugin-app-backend/alpha')); ++backend.add(import('@backstage/plugin-proxy-backend/alpha')); ++backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); ++backend.add(import('@backstage/plugin-techdocs-backend/alpha')); + +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); +- const tokenManager = ServerTokenManager.noop(); ++// auth plugin ++backend.add(import('@backstage/plugin-auth-backend')); ++// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin ++backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); ++// See https://backstage.io/docs/auth/guest/provider + +- return (plugin: string): PluginEnvironment => { +- const logger = root.child({ type: 'plugin', plugin }); +- const database = databaseManager.forPlugin(plugin); +- const cache = cacheManager.forPlugin(plugin); +- return { logger, database, cache, config, reader, discovery, tokenManager }; +- }; +-} ++// catalog plugin ++backend.add(import('@backstage/plugin-catalog-backend/alpha')); ++backend.add( ++ import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ++); + +-async function main() { +- const config = await loadBackendConfig({ +- argv: process.argv, +- logger: getRootLogger(), +- }); +- const createEnv = makeCreateEnv(config); ++// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors ++backend.add(import('@backstage/plugin-catalog-backend-module-logs')); + +- const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); +- const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); +- const authEnv = useHotMemoize(module, () => createEnv('auth')); +- const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); +- const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); +- const searchEnv = useHotMemoize(module, () => createEnv('search')); +- const appEnv = useHotMemoize(module, () => createEnv('app')); ++// permission plugin ++backend.add(import('@backstage/plugin-permission-backend/alpha')); ++// See https://backstage.io/docs/permissions/getting-started for how to create your own permission policy ++backend.add( ++ import('@backstage/plugin-permission-backend-module-allow-all-policy'), ++); + +- const apiRouter = Router(); +- apiRouter.use('/catalog', await catalog(catalogEnv)); +- apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); +- apiRouter.use('/auth', await auth(authEnv)); +- apiRouter.use('/techdocs', await techdocs(techdocsEnv)); +- apiRouter.use('/proxy', await proxy(proxyEnv)); +- apiRouter.use('/search', await search(searchEnv)); +- apiRouter.use(notFoundHandler()); ++// search plugin ++backend.add(import('@backstage/plugin-search-backend/alpha')); + +- const service = createServiceBuilder(module) +- .loadConfig(config) +- .addRouter('/api', apiRouter) +- .addRouter('', await app(appEnv)); ++// search engine ++// See https://backstage.io/docs/features/search/search-engines ++backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); + +- await service.start().catch(err => { +- console.log(err); +- process.exit(1); +- }); +-} ++// search collators ++backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); ++backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); + +-module.hot?.accept(); +-main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); +- process.exit(1); +-}); ++// kubernetes ++backend.add(import('@backstage/plugin-kubernetes-backend/alpha')); ++ ++backend.start(); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +deleted file mode 100644 +index 07fb04f..0000000 +--- a/packages/backend/src/plugins/app.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { createRouter } from '@backstage/plugin-app-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +-}: PluginEnvironment): Promise { +- return await createRouter({ +- logger, +- config, +- appPackageName: 'app', +- }); +-} +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +deleted file mode 100644 +index 5216510..0000000 +--- a/packages/backend/src/plugins/auth.ts ++++ /dev/null +@@ -1,12 +0,0 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, database, discovery }); +-} +diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts +deleted file mode 100644 +index 876cb6b..0000000 +--- a/packages/backend/src/plugins/catalog.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +-import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const builder = await CatalogBuilder.create(env); +- builder.addProcessor(new ScaffolderEntitiesProcessor()); +- const { processingEngine, router } = await builder.build(); +- await processingEngine.start(); +- return router; +-} +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +deleted file mode 100644 +index 506f6d9..0000000 +--- a/packages/backend/src/plugins/proxy.ts ++++ /dev/null +@@ -1,11 +0,0 @@ +-import { createRouter } from '@backstage/plugin-proxy-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); +-} +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +deleted file mode 100644 +index 6be2e97..0000000 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ /dev/null +@@ -1,27 +0,0 @@ +-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'; +-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 }); +- +- return await createRouter({ +- containerRunner, +- logger, +- config, +- database, +- catalogClient, +- reader, +- }); +-} +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +deleted file mode 100644 +index f23b0c7..0000000 +--- a/packages/backend/src/plugins/search.ts ++++ /dev/null +@@ -1,54 +0,0 @@ +-import { useHotCleanup } from '@backstage/backend-common'; +-import { createRouter } from '@backstage/plugin-search-backend'; +-import { +- IndexBuilder, +- LunrSearchEngine, +-} from '@backstage/plugin-search-backend-node'; +-import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend'; +- +-export default async function createPlugin({ +- logger, +- discovery, +- config, +- tokenManager, +-}: PluginEnvironment) { +- // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); +- +- // Collators are responsible for gathering documents known to plugins. This +- // collator gathers entities from the software catalog. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { +- discovery, +- tokenManager, +- }), +- }); +- +- // collator gathers entities from techdocs. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultTechDocsCollator.fromConfig(config, { +- discovery, +- logger, +- tokenManager, +- }), +- }); +- +- // The scheduler controls when documents are gathered from collators and sent +- // to the search engine for indexing. +- const { scheduler } = await indexBuilder.build(); +- +- // 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()); +- +- return await createRouter({ +- engine: indexBuilder.getSearchEngine(), +- logger, +- }); +-} +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +deleted file mode 100644 +index 054c64d..0000000 +--- a/packages/backend/src/plugins/techdocs.ts ++++ /dev/null +@@ -1,55 +0,0 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; +-import { +- createRouter, +- Generators, +- Preparers, +- Publisher, +-} from '@backstage/plugin-techdocs-backend'; +-import Docker from 'dockerode'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +- cache, +-}: PluginEnvironment): Promise { +- // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, +- }); +- +- // Docker client (conditionally) used by the generators, based on techdocs.generators config. +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- +- // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, +- containerRunner, +- }); +- +- // Publisher is used for +- // 1. Publishing generated files to storage +- // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, +- }); +- +- // checks if the publisher is working and logs the result +- await publisher.getReadiness(); +- +- return await createRouter({ +- preparers, +- generators, +- publisher, +- logger, +- config, +- discovery, +- cache, +- }); +-} +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +deleted file mode 100644 +index b1e2e0a..0000000 +--- a/packages/backend/src/types.ts ++++ /dev/null +@@ -1,19 +0,0 @@ +-import { Logger } from 'winston'; +-import { Config } from '@backstage/config'; +-import { +- PluginCacheManager, +- PluginDatabaseManager, +- PluginEndpointDiscovery, +- TokenManager, +- UrlReader, +-} from '@backstage/backend-common'; +- +-export type PluginEnvironment = { +- logger: Logger; +- database: PluginDatabaseManager; +- cache: PluginCacheManager; +- config: Config; +- reader: UrlReader; +- discovery: PluginEndpointDiscovery; +- tokenManager: TokenManager; +-}; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..733be13 +--- /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 dev', ++ 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..c37fc61 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1,3 @@ ++# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. ++# yarn lockfile v1 ++ diff --git a/diffs/0.4.9..0.5.18.diff b/diffs/0.4.9..0.5.18.diff new file mode 100644 index 00000000..e799aba0 --- /dev/null +++ b/diffs/0.4.9..0.5.18.diff @@ -0,0 +1,2089 @@ +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..5d426f5 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,51 @@ 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/apis/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 ++ ++auth: ++ providers: ++ guest: {} ++ ++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: ++ # Local example data, replace this with your production config, these are intended for demo use only. ++ # File locations are relative to the backend process, typically in a deployed context, such as in a Docker container, this will be the root ++ - type: file ++ target: ./examples/entities.yaml ++ ++ # Local example template ++ - type: file ++ target: ./examples/template/template.yaml ++ rules: ++ - allow: [Template] ++ ++ # Local example organizational data ++ - type: file ++ target: ./examples/org.yaml ++ rules: ++ - allow: [User, Group] +diff --git a/app-config.yaml b/app-config.yaml +index 1a622a2..ca52ec5 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/integrations/github/locations#configuration + 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 + +@@ -58,3 +66,5 @@ auth: + # see https://backstage.io/docs/auth/ to learn about auth providers +- providers: {} ++ providers: ++ # See https://backstage.io/docs/auth/guest/provider ++ guest: {} + +@@ -64,41 +74,40 @@ 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 ++ # Local example template ++ - type: file ++ target: ../../examples/template/template.yaml ++ rules: ++ - allow: [Template] + +- # Backstage example APIs +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml ++ # Local example organizational data ++ - type: file ++ target: ../../examples/org.yaml ++ rules: ++ - allow: [User, Group] + +- # Backstage example resources +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-resources.yaml ++ ## Uncomment these lines to add more example data ++ # - type: url ++ # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml + +- # Backstage example organization groups +- - type: url +- target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme/org.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] + +- # 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 +- 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 +- rules: +- - allow: [Template] ++kubernetes: ++ # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options ++ ++# see https://backstage.io/docs/permissions/getting-started for more on the permission framework ++permission: ++ # setting this to `false` will disable permissions ++ enabled: true +diff --git a/backstage.json b/backstage.json +index b565e30..230d39d 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "0.4.9" ++ "version": "1.30.0" + } +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..80a963e 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.27.0", ++ "@backstage/e2e-test-utils": "^0.1.1", ++ "@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": "^10.0.0", ++ "prettier": "^2.3.2", ++ "typescript": "~5.4.0" ++ }, ++ "resolutions": { ++ "@types/react": "^18", ++ "@types/react-dom": "^18" + }, +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..839ff88 +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,27 @@ ++/* ++ * 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('/'); ++ ++ const enterButton = page.getByRole('button', { name: 'Enter' }); ++ await expect(enterButton).toBeVisible(); ++ await enterButton.click(); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index ba91715..346ffc1 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -5,22 +5,37 @@ + "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.5.10", ++ "@backstage/catalog-model": "^1.6.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/core-app-api": "^1.14.2", ++ "@backstage/core-components": "^0.14.10", ++ "@backstage/core-plugin-api": "^1.9.3", ++ "@backstage/integration-react": "^1.1.30", ++ "@backstage/plugin-api-docs": "^0.11.8", ++ "@backstage/plugin-catalog": "^1.22.0", ++ "@backstage/plugin-catalog-common": "^1.0.26", ++ "@backstage/plugin-catalog-graph": "^0.4.8", ++ "@backstage/plugin-catalog-import": "^0.12.2", ++ "@backstage/plugin-catalog-react": "^1.12.3", ++ "@backstage/plugin-kubernetes": "^0.11.13", ++ "@backstage/plugin-org": "^0.6.28", ++ "@backstage/plugin-permission-react": "^0.4.25", ++ "@backstage/plugin-scaffolder": "^1.24.0", ++ "@backstage/plugin-search": "^1.4.15", ++ "@backstage/plugin-search-react": "^1.7.14", ++ "@backstage/plugin-techdocs": "^1.10.8", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.13", ++ "@backstage/plugin-techdocs-react": "^1.2.7", ++ "@backstage/plugin-user-settings": "^0.8.11", ++ "@backstage/theme": "^0.5.6", + "@material-ui/core": "^4.12.2", +@@ -28,31 +43,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": "^18.0.2", ++ "react-dom": "^18.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", +- "@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", ++ "@backstage/test-utils": "^1.5.10", ++ "@playwright/test": "^1.32.3", ++ "@testing-library/jest-dom": "^6.0.0", ++ "@testing-library/react": "^14.0.0", ++ "@testing-library/user-event": "^14.0.0", ++ "@testing-library/dom": "^9.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..18da7c4 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -8,5 +8,4 @@ + name="description" +- content="Backstage is an open platform for building developer portals" ++ content="Backstage is an open source framework for building developer portals" + /> +- + +diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx +index b94cac7..ec8ba1d 100644 +--- a/packages/app/src/App.test.tsx ++++ b/packages/app/src/App.test.tsx +@@ -1,3 +1,3 @@ + import React from 'react'; +-import { renderWithEffects } from '@backstage/test-utils'; ++import { render, waitFor } from '@testing-library/react'; + import App from './App'; +@@ -22,4 +22,7 @@ describe('App', () => { + +- const rendered = await renderWithEffects(); +- expect(rendered.baseElement).toBeInTheDocument(); ++ const rendered = render(); ++ ++ await waitFor(() => { ++ expect(rendered.baseElement).toBeInTheDocument(); ++ }); + }); +diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx +index 8a53583..e24c468 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,6 +13,5 @@ import { + import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; ++import { orgPlugin } from '@backstage/plugin-org'; + import { SearchPage } from '@backstage/plugin-search'; +-import { TechRadarPage } from '@backstage/plugin-tech-radar'; + import { +- DefaultTechDocsHome, + TechDocsIndexPage, +@@ -21,2 +20,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'; +@@ -27,5 +28,12 @@ import { Root } from './components/Root'; + +-import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; ++import { ++ AlertDisplay, ++ OAuthRequestDialog, ++ SignInPage, ++} 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 +45,6 @@ const app = createApp({ + viewTechDoc: techdocsPlugin.routes.docRoot, ++ createFromTemplate: scaffolderPlugin.routes.selectedTemplate, + }); + bind(apiDocsPlugin.externalRoutes, { +- createComponent: scaffolderPlugin.routes.root, ++ registerApi: catalogImportPlugin.routes.importPage, + }); +@@ -43,3 +52,10 @@ const app = createApp({ + registerComponent: catalogImportPlugin.routes.importPage, ++ viewTechDoc: techdocsPlugin.routes.docRoot, + }); ++ bind(orgPlugin.externalRoutes, { ++ catalogIndex: catalogPlugin.routes.catalogIndex, ++ }); ++ }, ++ components: { ++ SignInPage: props => , + }, +@@ -47,8 +63,5 @@ const app = createApp({ + +-const AppProvider = app.getProvider(); +-const AppRouter = app.getRouter(); +- + const routes = ( + +- ++ } /> + } /> +@@ -60,5 +73,3 @@ const routes = ( + +- }> +- +- ++ } /> + } +- /> ++ > ++ ++ ++ ++ + } /> +@@ -70,6 +85,9 @@ const routes = ( + } ++ path="/catalog-import" ++ element={ ++ ++ ++ ++ } + /> +- } /> + }> +@@ -78,2 +96,3 @@ const routes = ( + } /> ++ } /> + +@@ -81,4 +100,4 @@ const routes = ( + +-const App = () => ( +- ++export default app.createRoot( ++ <> + +@@ -88,5 +107,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..853d175 100644 +--- a/packages/app/src/components/Root/Root.tsx ++++ b/packages/app/src/components/Root/Root.tsx +@@ -1,22 +1,5 @@ +-/* +- * 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'; + import ExtensionIcon from '@material-ui/icons/Extension'; +-import MapIcon from '@material-ui/icons/MyLocation'; + import LibraryBooks from '@material-ui/icons/LibraryBooks'; +@@ -25,18 +8,23 @@ 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'; ++import { MyGroupsSidebarItem } from '@backstage/plugin-org'; ++import GroupIcon from '@material-ui/icons/People'; + +@@ -59,3 +47,3 @@ const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebarOpenState(); + +@@ -63,8 +51,3 @@ const SidebarLogo = () => { +
+- ++ + {isOpen ? : } +@@ -79,19 +62,32 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + +- ++ } to="/search"> + +- ++ + +- {/* Global nav, not org-specific */} +- +- +- +- +- {/* End global nav */} +- +- +- +- ++ }> ++ {/* Global nav, not org-specific */} ++ ++ ++ ++ ++ ++ {/* End global nav */} ++ ++ ++ {/* Items in this group will be scrollable if they run out of space */} ++ ++ + + +- ++ } ++ 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..6164c76 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,7 +27,5 @@ import { + isOrphan, ++ hasRelationWarnings, ++ EntityRelationWarning, + } from '@backstage/plugin-catalog'; +-import { +- isGithubActionsAvailable, +- EntityGithubActionsContent, +-} from '@backstage/plugin-github-actions'; + import { +@@ -56,2 +38,32 @@ 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'; ++ ++import { ++ EntityKubernetesContent, ++ isKubernetesAvailable, ++} from '@backstage/plugin-kubernetes'; ++ ++const techdocsContent = ( ++ ++ ++ ++ ++ ++); + +@@ -61,5 +73,9 @@ const cicdContent = ( + +- +- +- ++ {/* ++ Here you can add support for different CI/CD services, for example ++ using @backstage-community/plugin-github-actions as follows: ++ ++ ++ ++ */} + +@@ -94,2 +110,10 @@ const entityWarningContent = ( + ++ ++ ++ ++ ++ ++ ++ ++ + +@@ -110,2 +134,6 @@ const overviewContent = ( + ++ ++ ++ ++ + +@@ -129,2 +157,10 @@ const serviceEntityPage = ( + ++ ++ ++ ++ + +@@ -152,3 +188,3 @@ const serviceEntityPage = ( + +- ++ {techdocsContent} + +@@ -167,2 +203,10 @@ const websiteEntityPage = ( + ++ ++ ++ ++ + +@@ -179,3 +223,3 @@ const websiteEntityPage = ( + +- ++ {techdocsContent} + +@@ -198,3 +242,3 @@ const defaultEntityPage = ( + +- ++ {techdocsContent} + +@@ -225,2 +269,5 @@ const apiPage = ( + ++ ++ ++ + +@@ -276,5 +323,8 @@ const groupPage = ( + +- ++ + + ++ ++ ++ + +@@ -292,3 +342,9 @@ const systemPage = ( + +- ++ ++ ++ ++ ++ ++ ++ + +@@ -304,3 +360,19 @@ const systemPage = ( + +- ++ + +@@ -317,2 +389,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/app/src/index.tsx b/packages/app/src/index.tsx +index b16aaf7..d875c77 100644 +--- a/packages/app/src/index.tsx ++++ b/packages/app/src/index.tsx +@@ -2,5 +2,5 @@ import '@backstage/cli/asset-types'; + import React from 'react'; +-import ReactDOM from 'react-dom'; ++import ReactDOM from 'react-dom/client'; + import App from './App'; + +-ReactDOM.render(, document.getElementById('root')); ++ReactDOM.createRoot(document.getElementById('root')!).render(); +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..bef67b5 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,15 @@ 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 ++ ++# This will include the examples, if you don't need these simply remove this line ++COPY --chown=node:node examples ./examples + + # 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..6a14abc 100644 +--- a/packages/backend/package.json ++++ b/packages/backend/package.json +@@ -6,32 +6,43 @@ + "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", +- "dockerode": "^3.3.1", +- "express": "^4.17.1", +- "express-promise-router": "^4.1.0", +- "knex": "^0.21.6", +- "sqlite3": "^5.0.1", ++ "@backstage/backend-common": "^0.24.0", ++ "@backstage/backend-defaults": "^0.4.2", ++ "@backstage/backend-tasks": "^0.6.0", ++ "@backstage/config": "^1.2.0", ++ "@backstage/plugin-app-backend": "^0.3.72", ++ "@backstage/plugin-auth-backend": "^0.22.10", ++ "@backstage/plugin-auth-backend-module-github-provider": "^0.1.20", ++ "@backstage/plugin-auth-backend-module-guest-provider": "^0.1.9", ++ "@backstage/plugin-auth-node": "^0.5.0", ++ "@backstage/plugin-catalog-backend": "^1.25.0", ++ "@backstage/plugin-catalog-backend-module-logs": "^0.0.2", ++ "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.21", ++ "@backstage/plugin-kubernetes-backend": "^0.18.4", ++ "@backstage/plugin-permission-backend": "^0.5.47", ++ "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.1.20", ++ "@backstage/plugin-permission-common": "^0.8.1", ++ "@backstage/plugin-permission-node": "^0.8.1", ++ "@backstage/plugin-proxy-backend": "^0.5.4", ++ "@backstage/plugin-scaffolder-backend": "^1.24.0", ++ "@backstage/plugin-search-backend": "^1.5.15", ++ "@backstage/plugin-search-backend-module-catalog": "^0.2.0", ++ "@backstage/plugin-search-backend-module-pg": "^0.5.33", ++ "@backstage/plugin-search-backend-module-techdocs": "^0.2.0", ++ "@backstage/plugin-search-backend-node": "^1.3.0", ++ "@backstage/plugin-techdocs-backend": "^1.10.10", ++ "app": "link:../app", ++ "better-sqlite3": "^9.0.0", ++ "node-gyp": "^10.0.0", ++ "pg": "^8.11.3", + "winston": "^3.2.1" +@@ -39,6 +50,6 @@ + "devDependencies": { +- "@backstage/cli": "^0.10.3", +- "@types/dockerode": "^3.3.0", ++ "@backstage/cli": "^0.27.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.test.ts b/packages/backend/src/index.test.ts +deleted file mode 100644 +index 7814b8c..0000000 +--- a/packages/backend/src/index.test.ts ++++ /dev/null +@@ -1,8 +0,0 @@ +-import { PluginEnvironment } from './types'; +- +-describe('test', () => { +- it('unbreaks the test runner', () => { +- const unbreaker = {} as PluginEnvironment; +- expect(unbreaker).toBeTruthy(); +- }); +-}); +diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts +index 3f12122..f57d4c5 100644 +--- a/packages/backend/src/index.ts ++++ b/packages/backend/src/index.ts +@@ -8,83 +8,47 @@ + +-import Router from 'express-promise-router'; +-import { +- createServiceBuilder, +- loadBackendConfig, +- getRootLogger, +- useHotMemoize, +- notFoundHandler, +- CacheManager, +- DatabaseManager, +- SingleHostDiscovery, +- UrlReaders, +- ServerTokenManager, +-} from '@backstage/backend-common'; +-import { Config } from '@backstage/config'; +-import app from './plugins/app'; +-import auth from './plugins/auth'; +-import catalog from './plugins/catalog'; +-import scaffolder from './plugins/scaffolder'; +-import proxy from './plugins/proxy'; +-import techdocs from './plugins/techdocs'; +-import search from './plugins/search'; +-import { PluginEnvironment } from './types'; ++import { createBackend } from '@backstage/backend-defaults'; + +-function makeCreateEnv(config: Config) { +- const root = getRootLogger(); +- const reader = UrlReaders.default({ logger: root, config }); +- const discovery = SingleHostDiscovery.fromConfig(config); ++const backend = createBackend(); + +- root.info(`Created UrlReader ${reader}`); ++backend.add(import('@backstage/plugin-app-backend/alpha')); ++backend.add(import('@backstage/plugin-proxy-backend/alpha')); ++backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); ++backend.add(import('@backstage/plugin-techdocs-backend/alpha')); + +- const cacheManager = CacheManager.fromConfig(config); +- const databaseManager = DatabaseManager.fromConfig(config); +- const tokenManager = ServerTokenManager.noop(); ++// auth plugin ++backend.add(import('@backstage/plugin-auth-backend')); ++// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin ++backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); ++// See https://backstage.io/docs/auth/guest/provider + +- return (plugin: string): PluginEnvironment => { +- const logger = root.child({ type: 'plugin', plugin }); +- const database = databaseManager.forPlugin(plugin); +- const cache = cacheManager.forPlugin(plugin); +- return { logger, database, cache, config, reader, discovery, tokenManager }; +- }; +-} ++// catalog plugin ++backend.add(import('@backstage/plugin-catalog-backend/alpha')); ++backend.add( ++ import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ++); + +-async function main() { +- const config = await loadBackendConfig({ +- argv: process.argv, +- logger: getRootLogger(), +- }); +- const createEnv = makeCreateEnv(config); ++// See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors ++backend.add(import('@backstage/plugin-catalog-backend-module-logs')); + +- const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); +- const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); +- const authEnv = useHotMemoize(module, () => createEnv('auth')); +- const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); +- const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); +- const searchEnv = useHotMemoize(module, () => createEnv('search')); +- const appEnv = useHotMemoize(module, () => createEnv('app')); ++// permission plugin ++backend.add(import('@backstage/plugin-permission-backend/alpha')); ++// See https://backstage.io/docs/permissions/getting-started for how to create your own permission policy ++backend.add( ++ import('@backstage/plugin-permission-backend-module-allow-all-policy'), ++); + +- const apiRouter = Router(); +- apiRouter.use('/catalog', await catalog(catalogEnv)); +- apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); +- apiRouter.use('/auth', await auth(authEnv)); +- apiRouter.use('/techdocs', await techdocs(techdocsEnv)); +- apiRouter.use('/proxy', await proxy(proxyEnv)); +- apiRouter.use('/search', await search(searchEnv)); +- apiRouter.use(notFoundHandler()); ++// search plugin ++backend.add(import('@backstage/plugin-search-backend/alpha')); + +- const service = createServiceBuilder(module) +- .loadConfig(config) +- .addRouter('/api', apiRouter) +- .addRouter('', await app(appEnv)); ++// search engine ++// See https://backstage.io/docs/features/search/search-engines ++backend.add(import('@backstage/plugin-search-backend-module-pg/alpha')); + +- await service.start().catch(err => { +- console.log(err); +- process.exit(1); +- }); +-} ++// search collators ++backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); ++backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); + +-module.hot?.accept(); +-main().catch(error => { +- console.error(`Backend failed to start up, ${error}`); +- process.exit(1); +-}); ++// kubernetes ++backend.add(import('@backstage/plugin-kubernetes-backend/alpha')); ++ ++backend.start(); +diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts +deleted file mode 100644 +index 07fb04f..0000000 +--- a/packages/backend/src/plugins/app.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { createRouter } from '@backstage/plugin-app-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +-}: PluginEnvironment): Promise { +- return await createRouter({ +- logger, +- config, +- appPackageName: 'app', +- }); +-} +diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts +deleted file mode 100644 +index 5216510..0000000 +--- a/packages/backend/src/plugins/auth.ts ++++ /dev/null +@@ -1,12 +0,0 @@ +-import { createRouter } from '@backstage/plugin-auth-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- database, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, database, discovery }); +-} +diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts +deleted file mode 100644 +index 876cb6b..0000000 +--- a/packages/backend/src/plugins/catalog.ts ++++ /dev/null +@@ -1,14 +0,0 @@ +-import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +-import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin( +- env: PluginEnvironment, +-): Promise { +- const builder = await CatalogBuilder.create(env); +- builder.addProcessor(new ScaffolderEntitiesProcessor()); +- const { processingEngine, router } = await builder.build(); +- await processingEngine.start(); +- return router; +-} +diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts +deleted file mode 100644 +index 506f6d9..0000000 +--- a/packages/backend/src/plugins/proxy.ts ++++ /dev/null +@@ -1,11 +0,0 @@ +-import { createRouter } from '@backstage/plugin-proxy-backend'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +-}: PluginEnvironment): Promise { +- return await createRouter({ logger, config, discovery }); +-} +diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts +deleted file mode 100644 +index 6be2e97..0000000 +--- a/packages/backend/src/plugins/scaffolder.ts ++++ /dev/null +@@ -1,27 +0,0 @@ +-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'; +-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 }); +- +- return await createRouter({ +- containerRunner, +- logger, +- config, +- database, +- catalogClient, +- reader, +- }); +-} +diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts +deleted file mode 100644 +index f23b0c7..0000000 +--- a/packages/backend/src/plugins/search.ts ++++ /dev/null +@@ -1,54 +0,0 @@ +-import { useHotCleanup } from '@backstage/backend-common'; +-import { createRouter } from '@backstage/plugin-search-backend'; +-import { +- IndexBuilder, +- LunrSearchEngine, +-} from '@backstage/plugin-search-backend-node'; +-import { PluginEnvironment } from '../types'; +-import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend'; +-import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend'; +- +-export default async function createPlugin({ +- logger, +- discovery, +- config, +- tokenManager, +-}: PluginEnvironment) { +- // Initialize a connection to a search engine. +- const searchEngine = new LunrSearchEngine({ logger }); +- const indexBuilder = new IndexBuilder({ logger, searchEngine }); +- +- // Collators are responsible for gathering documents known to plugins. This +- // collator gathers entities from the software catalog. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultCatalogCollator.fromConfig(config, { +- discovery, +- tokenManager, +- }), +- }); +- +- // collator gathers entities from techdocs. +- indexBuilder.addCollator({ +- defaultRefreshIntervalSeconds: 600, +- collator: DefaultTechDocsCollator.fromConfig(config, { +- discovery, +- logger, +- tokenManager, +- }), +- }); +- +- // The scheduler controls when documents are gathered from collators and sent +- // to the search engine for indexing. +- const { scheduler } = await indexBuilder.build(); +- +- // 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()); +- +- return await createRouter({ +- engine: indexBuilder.getSearchEngine(), +- logger, +- }); +-} +diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts +deleted file mode 100644 +index 054c64d..0000000 +--- a/packages/backend/src/plugins/techdocs.ts ++++ /dev/null +@@ -1,55 +0,0 @@ +-import { DockerContainerRunner } from '@backstage/backend-common'; +-import { +- createRouter, +- Generators, +- Preparers, +- Publisher, +-} from '@backstage/plugin-techdocs-backend'; +-import Docker from 'dockerode'; +-import { Router } from 'express'; +-import { PluginEnvironment } from '../types'; +- +-export default async function createPlugin({ +- logger, +- config, +- discovery, +- reader, +- cache, +-}: PluginEnvironment): Promise { +- // Preparers are responsible for fetching source files for documentation. +- const preparers = await Preparers.fromConfig(config, { +- logger, +- reader, +- }); +- +- // Docker client (conditionally) used by the generators, based on techdocs.generators config. +- const dockerClient = new Docker(); +- const containerRunner = new DockerContainerRunner({ dockerClient }); +- +- // Generators are used for generating documentation sites. +- const generators = await Generators.fromConfig(config, { +- logger, +- containerRunner, +- }); +- +- // Publisher is used for +- // 1. Publishing generated files to storage +- // 2. Fetching files from storage and passing them to TechDocs frontend. +- const publisher = await Publisher.fromConfig(config, { +- logger, +- discovery, +- }); +- +- // checks if the publisher is working and logs the result +- await publisher.getReadiness(); +- +- return await createRouter({ +- preparers, +- generators, +- publisher, +- logger, +- config, +- discovery, +- cache, +- }); +-} +diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts +deleted file mode 100644 +index b1e2e0a..0000000 +--- a/packages/backend/src/types.ts ++++ /dev/null +@@ -1,19 +0,0 @@ +-import { Logger } from 'winston'; +-import { Config } from '@backstage/config'; +-import { +- PluginCacheManager, +- PluginDatabaseManager, +- PluginEndpointDiscovery, +- TokenManager, +- UrlReader, +-} from '@backstage/backend-common'; +- +-export type PluginEnvironment = { +- logger: Logger; +- database: PluginDatabaseManager; +- cache: PluginCacheManager; +- config: Config; +- reader: UrlReader; +- discovery: PluginEndpointDiscovery; +- tokenManager: TokenManager; +-}; +diff --git a/playwright.config.ts b/playwright.config.ts +new file mode 100644 +index 0000000..733be13 +--- /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 dev', ++ 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..c37fc61 +--- /dev/null ++++ b/yarn.lock +@@ -0,0 +1,3 @@ ++# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. ++# yarn lockfile v1 ++ diff --git a/diffs/0.5.0..0.5.18.diff b/diffs/0.5.0..0.5.18.diff new file mode 100644 index 00000000..89bc0000 --- /dev/null +++ b/diffs/0.5.0..0.5.18.diff @@ -0,0 +1,1150 @@ +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 8f0751c..5d426f5 100644 +--- a/app-config.production.yaml ++++ b/app-config.production.yaml +@@ -15,3 +15,3 @@ backend: + +- # config options: https://node-postgres.com/api/client ++ # config options: https://node-postgres.com/apis/client + database: +@@ -30,2 +30,6 @@ backend: + ++auth: ++ providers: ++ guest: {} ++ + catalog: +@@ -34,2 +38,18 @@ catalog: + # on how to get entities into the catalog. +- locations: [] ++ locations: ++ # Local example data, replace this with your production config, these are intended for demo use only. ++ # File locations are relative to the backend process, typically in a deployed context, such as in a Docker container, this will be the root ++ - type: file ++ target: ./examples/entities.yaml ++ ++ # Local example template ++ - type: file ++ target: ./examples/template/template.yaml ++ rules: ++ - allow: [Template] ++ ++ # Local example organizational data ++ - type: file ++ target: ./examples/org.yaml ++ rules: ++ - allow: [User, Group] +diff --git a/app-config.yaml b/app-config.yaml +index 3d216ec..ca52ec5 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 +@@ -40,3 +38,3 @@ integrations: + # 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 ++ # about setting up the GitHub integration here: https://backstage.io/docs/integrations/github/locations#configuration + token: ${GITHUB_TOKEN} +@@ -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 + +@@ -67,3 +66,5 @@ auth: + # see https://backstage.io/docs/auth/ to learn about auth providers +- providers: {} ++ providers: ++ # See https://backstage.io/docs/auth/guest/provider ++ guest: {} + +@@ -104 +105,9 @@ catalog: + # - allow: [User, Group] ++ ++kubernetes: ++ # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options ++ ++# see https://backstage.io/docs/permissions/getting-started for more on the permission framework ++permission: ++ # setting this to `false` will disable permissions ++ enabled: true +diff --git a/backstage.json b/backstage.json +index a21904c..230d39d 100644 +--- a/backstage.json ++++ b/backstage.json +@@ -1,3 +1,3 @@ + { +- "version": "1.13.0" ++ "version": "1.30.0" + } +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..80a963e 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,13 +33,15 @@ + "devDependencies": { +- "@backstage/cli": "^0.22.6", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/e2e-test-utils": "^0.1.1", ++ "@playwright/test": "^1.32.3", + "@spotify/prettier-config": "^12.0.0", +- "concurrently": "^6.0.0", +- "lerna": "^4.0.0", +- "node-gyp": "^9.0.0", ++ "concurrently": "^8.0.0", ++ "lerna": "^7.3.0", ++ "node-gyp": "^10.0.0", + "prettier": "^2.3.2", +- "typescript": "~5.0.0" ++ "typescript": "~5.4.0" + }, + "resolutions": { +- "@types/react": "^17", +- "@types/react-dom": "^17" ++ "@types/react": "^18", ++ "@types/react-dom": "^18" + }, +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..839ff88 +--- /dev/null ++++ b/packages/app/e2e-tests/app.test.ts +@@ -0,0 +1,27 @@ ++/* ++ * 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('/'); ++ ++ const enterButton = page.getByRole('button', { name: 'Enter' }); ++ await expect(enterButton).toBeVisible(); ++ await enterButton.click(); ++ ++ await expect(page.getByText('My Company Catalog')).toBeVisible(); ++}); +diff --git a/packages/app/package.json b/packages/app/package.json +index 84eef05..346ffc1 100644 +--- a/packages/app/package.json ++++ b/packages/app/package.json +@@ -13,34 +13,29 @@ + "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.5.10", ++ "@backstage/catalog-model": "^1.6.0", ++ "@backstage/cli": "^0.27.0", ++ "@backstage/core-app-api": "^1.14.2", ++ "@backstage/core-components": "^0.14.10", ++ "@backstage/core-plugin-api": "^1.9.3", ++ "@backstage/integration-react": "^1.1.30", ++ "@backstage/plugin-api-docs": "^0.11.8", ++ "@backstage/plugin-catalog": "^1.22.0", ++ "@backstage/plugin-catalog-common": "^1.0.26", ++ "@backstage/plugin-catalog-graph": "^0.4.8", ++ "@backstage/plugin-catalog-import": "^0.12.2", ++ "@backstage/plugin-catalog-react": "^1.12.3", ++ "@backstage/plugin-kubernetes": "^0.11.13", ++ "@backstage/plugin-org": "^0.6.28", ++ "@backstage/plugin-permission-react": "^0.4.25", ++ "@backstage/plugin-scaffolder": "^1.24.0", ++ "@backstage/plugin-search": "^1.4.15", ++ "@backstage/plugin-search-react": "^1.7.14", ++ "@backstage/plugin-techdocs": "^1.10.8", ++ "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.13", ++ "@backstage/plugin-techdocs-react": "^1.2.7", ++ "@backstage/plugin-user-settings": "^0.8.11", ++ "@backstage/theme": "^0.5.6", + "@material-ui/core": "^4.12.2", +@@ -48,4 +43,4 @@ + "history": "^5.0.0", +- "react": "^17.0.2", +- "react-dom": "^17.0.2", ++ "react": "^18.0.2", ++ "react-dom": "^18.0.2", + "react-router": "^6.3.0", +@@ -55,13 +50,10 @@ + "devDependencies": { +- "@backstage/test-utils": "^1.3.0", +- "@testing-library/jest-dom": "^5.10.1", +- "@testing-library/react": "^12.1.3", ++ "@backstage/test-utils": "^1.5.10", ++ "@playwright/test": "^1.32.3", ++ "@testing-library/jest-dom": "^6.0.0", ++ "@testing-library/react": "^14.0.0", + "@testing-library/user-event": "^14.0.0", +- "@testing-library/dom": "^8.0.0", +- "@types/node": "^16.11.26", ++ "@testing-library/dom": "^9.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..18da7c4 100644 +--- a/packages/app/public/index.html ++++ b/packages/app/public/index.html +@@ -8,5 +8,4 @@ + name="description" +- content="Backstage is an open platform for building developer portals" ++ content="Backstage is an open source framework for building developer portals" + /> +- +