Merge pull request #19486 from backstage/mob/di-experiments

Initial experimental implementation of declarative integration for frontend
This commit is contained in:
Patrik Oldsberg
2023-08-22 15:31:04 +02:00
committed by GitHub
39 changed files with 2178 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
+3
View File
@@ -0,0 +1,3 @@
# example-app-next
This package is an example of a Backstage application.
+108
View File
@@ -0,0 +1,108 @@
app:
packages: 'all' # ✨
extensions:
- core.router
- graphiql.page:
config:
path: /
# - core.signInPage:
# props:
# provider:
# id: google
# title: Google
# message: Sign In using Google
# apiRef: googleAuthApiRef # ???
# - core.nav:
# config:
# logo: assets/logo.png
# layout:
# - label: Search
# icon: search
# to: /search
# items:
# - point: search
# - type: divider
# - label: Menu
# icon: menu
# items:
# - label: Home
# icon: home
# to: /catalog
# - label: Create...
# icon: create
# to: /create
# - type: divider
# - type: scroll-wrapper
# items:
# - label: Tech Radar
# icon: map
# to: /tech-radar
# - label: GraphiQL
# icon: graphiql
# to: /graphiql
# - type: divider
# - point: shortcuts
# - type: space
# - type: divider
# - label: Settings
# icon: avatar
# to: /settings
# items:
# - point: settings
# - core.nav/search: '@backstage/plugin-search#SidebarSearchModal'
# - core.nav/shortcuts:
# use: '@backstage/plugin-shortcuts#Shortcuts'
# config:
# allowExternalLinks: true
# - core.nav/settings: '@backstage/plugin-user-settings#SidebarSettings'
# - core.pages.index:
# at:
# point: core.routes
# config:
# path: /
# use: 'react-router-dom#Navigate'
# config:
# to: /catalog
# - scaffolder.page:
# config:
# groups:
# - title: Recommended
# filter:
# entity.metadata.tags: recommended
# - scaffolder.page/fields: '@backstage/plugin-scaffolder#LowerCaseValuePickerFieldExtension'
# - scaffolder.page/layout: '@backstage/plugin-scaffolder#TwoColumnLayout'
# - search.page/content: 'app#customSearchPage' # custom search page from somewhere
# - user-settings.page.routes.advanced:
# at:
# point: user-settings.page/routes
# config:
# title: Advanced
# path: /advanced
# use: '@backstage/plugin-user-settings#AdvancedSettings'
# - entity.card.orphanWarning
# - entity.card.processingErrors
# - entity.card.about
# - entity.card.catalogGraph
# - entity.card.pagerDuty
# - entity.card.links
# - entity.card.labels
# - entity.card.githubInsightsLanguages
# - entity.card.githubInsightsReleases
# - entity.card.githubInsightsReadme:
# config:
# maxHeight: 350 # Throwing this config in to have an example, but in practice rely on default
# - entity.card.subcomponentsCard
# - entity.card.userProfile
# - entity.card.ownership
# - entity.card.likeDislikeRatings
# - entity.content.dependsOnComponents
# - entity.content.codeInsights
# - entity.content.todo
# - entity.content.feedbackResponse
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 69 KiB

+143
View File
@@ -0,0 +1,143 @@
{
"name": "example-app-next",
"version": "0.0.0",
"private": true,
"backstage": {
"role": "frontend"
},
"bundled": true,
"dependencies": {
"@backstage/app-defaults": "workspace:^",
"@backstage/catalog-model": "workspace:^",
"@backstage/cli": "workspace:^",
"@backstage/config": "workspace:^",
"@backstage/core-app-api": "workspace:^",
"@backstage/core-components": "workspace:^",
"@backstage/core-plugin-api": "workspace:^",
"@backstage/frontend-app-api": "workspace:^",
"@backstage/frontend-plugin-api": "workspace:^",
"@backstage/integration-react": "workspace:^",
"@backstage/plugin-adr": "workspace:^",
"@backstage/plugin-airbrake": "workspace:^",
"@backstage/plugin-apache-airflow": "workspace:^",
"@backstage/plugin-api-docs": "workspace:^",
"@backstage/plugin-azure-devops": "workspace:^",
"@backstage/plugin-azure-sites": "workspace:^",
"@backstage/plugin-badges": "workspace:^",
"@backstage/plugin-catalog-common": "workspace:^",
"@backstage/plugin-catalog-graph": "workspace:^",
"@backstage/plugin-catalog-import": "workspace:^",
"@backstage/plugin-catalog-react": "workspace:^",
"@backstage/plugin-catalog-unprocessed-entities": "workspace:^",
"@backstage/plugin-circleci": "workspace:^",
"@backstage/plugin-cloudbuild": "workspace:^",
"@backstage/plugin-code-coverage": "workspace:^",
"@backstage/plugin-cost-insights": "workspace:^",
"@backstage/plugin-devtools": "workspace:^",
"@backstage/plugin-dynatrace": "workspace:^",
"@backstage/plugin-entity-feedback": "workspace:^",
"@backstage/plugin-explore": "workspace:^",
"@backstage/plugin-gcalendar": "workspace:^",
"@backstage/plugin-gcp-projects": "workspace:^",
"@backstage/plugin-github-actions": "workspace:^",
"@backstage/plugin-gocd": "workspace:^",
"@backstage/plugin-graphiql": "workspace:^",
"@backstage/plugin-home": "workspace:^",
"@backstage/plugin-jenkins": "workspace:^",
"@backstage/plugin-kafka": "workspace:^",
"@backstage/plugin-kubernetes": "workspace:^",
"@backstage/plugin-lighthouse": "workspace:^",
"@backstage/plugin-linguist": "workspace:^",
"@backstage/plugin-linguist-common": "workspace:^",
"@backstage/plugin-microsoft-calendar": "workspace:^",
"@backstage/plugin-newrelic": "workspace:^",
"@backstage/plugin-newrelic-dashboard": "workspace:^",
"@backstage/plugin-octopus-deploy": "workspace:^",
"@backstage/plugin-org": "workspace:^",
"@backstage/plugin-pagerduty": "workspace:^",
"@backstage/plugin-permission-react": "workspace:^",
"@backstage/plugin-playlist": "workspace:^",
"@backstage/plugin-puppetdb": "workspace:^",
"@backstage/plugin-rollbar": "workspace:^",
"@backstage/plugin-scaffolder": "workspace:^",
"@backstage/plugin-scaffolder-react": "workspace:^",
"@backstage/plugin-search": "workspace:^",
"@backstage/plugin-search-common": "workspace:^",
"@backstage/plugin-search-react": "workspace:^",
"@backstage/plugin-sentry": "workspace:^",
"@backstage/plugin-shortcuts": "workspace:^",
"@backstage/plugin-stack-overflow": "workspace:^",
"@backstage/plugin-stackstorm": "workspace:^",
"@backstage/plugin-tech-insights": "workspace:^",
"@backstage/plugin-tech-radar": "workspace:^",
"@backstage/plugin-techdocs": "workspace:^",
"@backstage/plugin-techdocs-module-addons-contrib": "workspace:^",
"@backstage/plugin-techdocs-react": "workspace:^",
"@backstage/plugin-todo": "workspace:^",
"@backstage/plugin-user-settings": "workspace:^",
"@backstage/theme": "workspace:^",
"@internal/plugin-catalog-customized": "workspace:^",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.61",
"@octokit/rest": "^19.0.3",
"@oriflame/backstage-plugin-score-card": "^0.7.0",
"@roadiehq/backstage-plugin-buildkite": "^2.0.8",
"@roadiehq/backstage-plugin-github-insights": "^2.0.5",
"@roadiehq/backstage-plugin-github-pull-requests": "^2.2.7",
"@roadiehq/backstage-plugin-travis-ci": "^2.0.5",
"history": "^5.0.0",
"lodash": "^4.17.21",
"prop-types": "^15.7.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router": "^6.3.0",
"react-router-dom": "^6.3.0",
"react-use": "^17.2.4",
"zen-observable": "^0.10.0"
},
"devDependencies": {
"@backstage/test-utils": "workspace:^",
"@testing-library/cypress": "^9.0.0",
"@testing-library/dom": "^8.0.0",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^12.1.3",
"@testing-library/user-event": "^14.0.0",
"@types/jquery": "^3.3.34",
"@types/node": "^16.11.26",
"@types/react": "*",
"@types/react-dom": "*",
"@types/zen-observable": "^0.8.0",
"cross-env": "^7.0.0",
"cypress": "^10.0.0",
"eslint-plugin-cypress": "^2.10.3",
"start-server-and-test": "^1.10.11"
},
"scripts": {
"start": "backstage-cli package start --config ../../app-config.yaml --config app-config.yaml",
"build": "backstage-cli package build",
"clean": "backstage-cli package clean",
"test": "backstage-cli package test",
"lint": "backstage-cli package lint",
"test:e2e": "start-server-and-test start http://localhost:3000 cy:dev",
"test:e2e:ci": "start-server-and-test start http://localhost:3000 cy:run",
"cy:dev": "cypress open",
"cy:run": "cypress run"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"files": [
"dist"
],
"license": "Apache-2.0"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 883 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" id="favicon" viewBox="0 0 337.46 428.5"><title>Backstage favicon</title><style>#favicon path{fill:#121212}@media (prefers-color-scheme:dark){#favicon path{fill:#7df3e1}}</style><path d="M303,166.05a80.69,80.69,0,0,0,13.45-10.37c.79-.77,1.55-1.53,2.3-2.3a83.12,83.12,0,0,0,7.93-9.38A63.69,63.69,0,0,0,333,133.23a48.58,48.58,0,0,0,4.35-16.4c1.49-19.39-10-38.67-35.62-54.22L198.56,0,78.3,115.23,0,190.25l108.6,65.91a111.59,111.59,0,0,0,57.76,16.41c24.92,0,48.8-8.8,66.42-25.69,19.16-18.36,25.52-42.12,13.7-61.87a49.22,49.22,0,0,0-6.8-8.87A89.17,89.17,0,0,0,259,178.29h.15a85.08,85.08,0,0,0,31-5.79A80.88,80.88,0,0,0,303,166.05ZM202.45,225.86c-19.32,18.51-50.4,21.23-75.7,5.9L51.61,186.15l67.45-64.64,76.41,46.38C223,184.58,221.49,207.61,202.45,225.86Zm8.93-82.22-70.65-42.89L205.14,39,274.51,81.1c25.94,15.72,29.31,37,10.55,55A60.69,60.69,0,0,1,211.38,143.64Zm29.86,190c-19.57,18.75-46.17,29.09-74.88,29.09a123.73,123.73,0,0,1-64.1-18.2L0,282.52v24.67L108.6,373.1a111.6,111.6,0,0,0,57.76,16.42c24.92,0,48.8-8.81,66.42-25.69,12.88-12.34,20-27.13,19.68-41.49v-1.79A87.27,87.27,0,0,1,241.24,333.68Zm0-39c-19.57,18.75-46.17,29.08-74.88,29.08a123.81,123.81,0,0,1-64.1-18.19L0,243.53v24.68l108.6,65.91a111.6,111.6,0,0,0,57.76,16.42c24.92,0,48.8-8.81,66.42-25.69,12.88-12.34,20-27.13,19.68-41.5v-1.78A87.27,87.27,0,0,1,241.24,294.7Zm0-39c-19.57,18.76-46.17,29.09-74.88,29.09a123.81,123.81,0,0,1-64.1-18.19L0,204.55v24.68l108.6,65.91a111.59,111.59,0,0,0,57.76,16.41c24.92,0,48.8-8.8,66.42-25.68,12.88-12.35,20-27.13,19.68-41.5v-1.82A86.09,86.09,0,0,1,241.24,255.71Zm83.7,25.74a94.15,94.15,0,0,1-60.2,25.86h0V334a81.6,81.6,0,0,0,51.74-22.37c14-13.38,21.14-28.11,21-42.64v-2.19A94.92,94.92,0,0,1,324.94,281.45Zm-83.7,91.21c-19.57,18.76-46.17,29.09-74.88,29.09a123.73,123.73,0,0,1-64.1-18.2L0,321.5v24.68l108.6,65.9a111.6,111.6,0,0,0,57.76,16.42c24.92,0,48.8-8.8,66.42-25.69,12.88-12.34,20-27.13,19.68-41.49v-1.79A86.29,86.29,0,0,1,241.24,372.66ZM327,162.45c-.68.69-1.35,1.38-2.05,2.06a94.37,94.37,0,0,1-10.64,8.65,91.35,91.35,0,0,1-11.6,7,94.53,94.53,0,0,1-26.24,8.71,97.69,97.69,0,0,1-14.16,1.57c.5,1.61.9,3.25,1.25,4.9a53.27,53.27,0,0,1,1.14,12V217h.05a84.41,84.41,0,0,0,25.35-5.55,81,81,0,0,0,26.39-16.82c.8-.77,1.5-1.56,2.26-2.34a82.08,82.08,0,0,0,7.93-9.38A63.76,63.76,0,0,0,333,172.17a48.55,48.55,0,0,0,4.32-16.45c.09-1.23.2-2.47.19-3.7V150q-1.08,1.54-2.25,3.09A96.73,96.73,0,0,1,327,162.45Zm0,77.92c-.69.7-1.31,1.41-2,2.1a94.2,94.2,0,0,1-60.2,25.86h0l0,26.67h0a81.6,81.6,0,0,0,51.74-22.37A73.51,73.51,0,0,0,333,250.13a48.56,48.56,0,0,0,4.32-16.44c.09-1.24.2-2.47.19-3.71v-2.19c-.74,1.07-1.46,2.15-2.27,3.21A95.68,95.68,0,0,1,327,240.37Zm0-39c-.69.7-1.31,1.41-2,2.1a93.18,93.18,0,0,1-10.63,8.65,91.63,91.63,0,0,1-11.63,7,95.47,95.47,0,0,1-37.94,10.18h0V256h0a81.65,81.65,0,0,0,51.74-22.37c.8-.77,1.5-1.56,2.26-2.34a82.08,82.08,0,0,0,7.93-9.38A63.76,63.76,0,0,0,333,211.15a48.56,48.56,0,0,0,4.32-16.44c.09-1.24.2-2.48.19-3.71v-2.2c-.74,1.08-1.46,2.16-2.27,3.22A95.68,95.68,0,0,1,327,201.39Z"/></svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

+98
View File
@@ -0,0 +1,98 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Backstage is an open platform for building developer portals"
/>
<link rel="apple-touch-icon" href="<%= publicPath %>/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link
rel="manifest"
href="<%= publicPath %>/manifest.json"
crossorigin="use-credentials"
/>
<link rel="icon" href="<%= publicPath %>/favicon.ico" />
<link rel="shortcut icon" href="<%= publicPath %>/favicon.ico" />
<link
rel="apple-touch-icon"
sizes="180x180"
href="<%= publicPath %>/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="<%= publicPath %>/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="<%= publicPath %>/favicon-16x16.png"
/>
<link
rel="mask-icon"
href="<%= publicPath %>/safari-pinned-tab.svg"
color="#5bbad5"
/>
<title><%= config.getString('app.title') %></title>
<% if (config.has('app.datadogRum')) { %>
<script>
(function (h, o, u, n, d) {
h = h[d] = h[d] || {
q: [],
onReady: function (c) {
h.q.push(c);
},
};
d = o.createElement(u);
d.async = 1;
d.src = n;
n = o.getElementsByTagName(u)[0];
n.parentNode.insertBefore(d, n);
})(
window,
document,
'script',
'https://www.datadoghq-browser-agent.com/datadog-rum-v3.js',
'DD_RUM',
);
DD_RUM.onReady(function () {
DD_RUM.init({
clientToken: '<%= config.getString("app.datadogRum.clientToken") %>',
applicationId:
'<%= config.getString("app.datadogRum.applicationId") %>',
site: '<%= config.getOptionalString("app.datadogRum.site") || "datadoghq.com" %>',
service: 'backstage',
env: '<%= config.getString("app.datadogRum.env") %>',
sampleRate: 100,
trackInteractions: true,
});
});
</script>
<% } %>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `yarn start`.
To create a production bundle, use `yarn build`.
-->
</body>
</html>
+15
View File
@@ -0,0 +1,15 @@
{
"short_name": "Backstage",
"name": "Backstage",
"icons": [
{
"src": "favicon.ico",
"sizes": "48x48",
"type": "image/png"
}
],
"start_url": "./index.html",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
+2
View File
@@ -0,0 +1,2 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="682.667" height="682.667" preserveAspectRatio="xMidYMid meet" version="1.0" viewBox="0 0 512 512"><metadata>Created by potrace 1.11, written by Peter Selinger 2001-2013</metadata><g fill="#000" stroke="none"><path d="M492 4610 c-4 -3 -8 -882 -7 -1953 l0 -1948 850 2 c898 1 945 3 1118 49 505 134 823 531 829 1037 2 136 -9 212 -44 323 -40 125 -89 218 -163 310 -35 43 -126 128 -169 157 -22 15 -43 30 -46 33 -12 13 -131 70 -188 91 l-64 22 60 28 c171 77 317 224 403 404 64 136 92 266 91 425 -5 424 -245 770 -642 923 -79 30 -105 39 -155 50 -11 3 -38 10 -60 15 -22 6 -60 13 -85 17 -25 3 -58 9 -75 12 -36 8 -1643 11 -1653 3z m1497 -743 c236 -68 352 -254 305 -486 -26 -124 -110 -224 -232 -277 -92 -40 -151 -46 -439 -49 l-283 -3 -1 27 c-1 36 -1 760 0 790 l1 23 298 -5 c226 -4 310 -9 351 -20z m-82 -1538 c98 -3 174 -19 247 -52 169 -78 257 -212 258 -395 0 -116 -36 -221 -100 -293 -64 -72 -192 -135 -314 -155 -23 -3 -181 -7 -350 -8 l-308 -2 -1 26 c-6 210 1 874 9 879 9 5 366 6 559 0z" transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"/><path d="M4160 1789 c-275 -24 -499 -263 -503 -536 -1 -115 21 -212 66 -292 210 -369 697 -402 950 -65 77 103 110 199 111 329 0 50 -6 113 -13 140 -16 58 -62 155 -91 193 -33 43 -122 132 -132 132 -5 0 -26 11 -46 25 -85 56 -219 85 -342 74z" transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"/></g></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

+48
View File
@@ -0,0 +1,48 @@
/*
* Copyright 2020 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES 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';
import { renderWithEffects } from '@backstage/test-utils';
describe('App', () => {
it('should render', async () => {
process.env = {
NODE_ENV: 'test',
APP_CONFIG: [
{
data: {
app: {
title: 'Test',
support: { url: 'http://localhost:7007/support' },
},
backend: { baseUrl: 'http://localhost:7007' },
lighthouse: {
baseUrl: 'http://localhost:3003',
},
techdocs: {
storageUrl: 'http://localhost:7007/api/techdocs/static/docs',
},
},
context: 'test',
},
] as any,
};
const { default: App } = await import('./App');
const rendered = await renderWithEffects(<App />);
expect(rendered.baseElement).toBeInTheDocument();
});
});
+113
View File
@@ -0,0 +1,113 @@
/*
* 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 { graphiqlPlugin as legacyGraphiqlPlugin } from '@backstage/plugin-graphiql';
import { createApp as createLegacyApp } from '@backstage/app-defaults';
import { createApp } from '@backstage/frontend-app-api';
import { graphiqlPlugin } from './examples/graphiqlPlugin';
/*
# Notes
TODO:
- proper createApp
- connect extensions and plugins, provide method?
- higher level API for creating standard extensions + higher order framework API for creating those?
- extension config schema + validation
- figure out how to resolve configured extension ref to runtime value, e.g. '@backstage/plugin-graphiql#GraphiqlPage'
- make sure all shorthands work + tests
- figure out package structure / how to ship, frontend-plugin-api/frontend-app-api
- figure out routing, useRouteRef in the new system
- Legacy plugins / interop
- dynamic updates, runtime API
*/
/* core */
// const discoverPackages = async () => {
// // stub for now, deferring package discovery til later
// return ['@backstage/plugin-graphiql'];
// };
/* graphiql package */
/* app.tsx */
const app = createApp({
plugins: [graphiqlPlugin],
// bindRoutes({ bind }) {
// bind(catalogPlugin.externalRoutes, {
// createComponent: scaffolderPlugin.routes.root,
// });
// bind(scaffolderPlugin.externalRoutes, {
// registerComponent: catalogImportPlugin.routes.importPage,
// });
// },
});
const legacyApp = createLegacyApp({ plugins: [legacyGraphiqlPlugin] });
export default legacyApp.createRoot(app.createRoot());
// const routes = (
// <FlatRoutes>
// {/* <Route path="/" element={<Navigate to="catalog" />} />
// <Route path="/catalog" element={<CatalogIndexPage />} />
// <Route
// path="/catalog/:namespace/:kind/:name"
// element={<CatalogEntityPage />}
// >
// <EntityLayout>
// <EntityLayout.Route path="/" title="Overview">
// <Grid container spacing={3} alignItems="stretch">
// <Grid item md={6} xs={12}>
// <EntityAboutCard variant="gridItem" />
// </Grid>
// <Grid item md={4} xs={12}>
// <EntityLinksCard />
// </Grid>
// </Grid>
// </EntityLayout.Route>
// <EntityLayout.Route path="/todos" title="TODOs">
// <EntityTodoContent />
// </EntityLayout.Route>
// </EntityLayout>
// </Route>
// <Route
// path="/catalog-import"
// element={
// <CatalogImportPage />
// }
// /> */}
// {/* <Route
// path="/tech-radar"
// element={<TechRadarPage width={1500} height={800} />}
// /> */}
// <Route path="/graphiql" element={<GraphiQLPage />} />
// </FlatRoutes>
// );
// export default app.createRoot(
// <>
// {/* <AlertDisplay transientTimeoutMs={2500} />
// <OAuthRequestDialog /> */}
// <AppRouter>{routes}</AppRouter>
// </>,
// );
@@ -0,0 +1,49 @@
/*
* 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 React from 'react';
import {
createExtension,
createPlugin,
coreExtensionData,
} from '@backstage/frontend-plugin-api';
import { Router as GraphiQLPage } from '@backstage/plugin-graphiql';
export const GraphiqlPageExtension = createExtension({
output: {
component: coreExtensionData.reactComponent,
path: coreExtensionData.routePath,
},
factory({ bind, config }) {
bind.component(() => {
return <GraphiQLPage />;
});
// TODO: In need of schemas and type safety
bind.path((config as { path: string }).path);
},
});
export const graphiqlPlugin = createPlugin({
id: 'graphiql',
defaultExtensionInstances: [
{
id: 'graphiql.page',
at: 'core.router/routes',
extension: GraphiqlPageExtension,
config: { path: '/graphiql' },
},
],
});
+22
View File
@@ -0,0 +1,22 @@
/*
* Copyright 2020 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import '@backstage/cli/asset-types';
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
+21
View File
@@ -0,0 +1,21 @@
/*
* Copyright 2020 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';
+1
View File
@@ -0,0 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
+12
View File
@@ -0,0 +1,12 @@
# @backstage/frontend-app-api
_This package was created through the Backstage CLI_.
## Installation
Install the package via Yarn:
```sh
cd <package-dir> # if within a monorepo
yarn add @backstage/frontend-app-api
```
+14
View File
@@ -0,0 +1,14 @@
## API Report File for "@backstage/frontend-app-api"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
/// <reference types="react" />
import { BackstagePlugin } from '@backstage/frontend-plugin-api';
// @public (undocumented)
export function createApp(options: { plugins: BackstagePlugin[] }): {
createRoot(): JSX.Element;
};
```
+35
View File
@@ -0,0 +1,35 @@
/*
* 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.
*/
export interface Config {
app?: {
/**
* @deepVisibility frontend
*/
extensions?:
| string
| {
[extensionId: string]:
| boolean
| string
| {
at?: string;
extension?: string;
config?: unknown;
};
};
};
}
+44
View File
@@ -0,0 +1,44 @@
{
"name": "@backstage/frontend-app-api",
"version": "0.0.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"private": true,
"publishConfig": {
"access": "public",
"main": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"backstage": {
"role": "web-library"
},
"scripts": {
"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",
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack"
},
"devDependencies": {
"@backstage/cli": "workspace:^",
"@testing-library/jest-dom": "^5.10.1"
},
"configSchema": "config.d.ts",
"files": [
"config.d.ts",
"dist"
],
"dependencies": {
"@backstage/config": "workspace:^",
"@backstage/frontend-plugin-api": "workspace:^",
"@backstage/plugin-graphiql": "workspace:^",
"lodash": "^4.17.21"
},
"peerDependencies": {
"react": "^16.13.1 || ^17.0.0",
"react-router-dom": "6.0.0-beta.0 || ^6.3.0"
}
}
+206
View File
@@ -0,0 +1,206 @@
/*
* 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 React from 'react';
import { Config, ConfigReader } from '@backstage/config';
import {
ExtensionInstanceConfig,
BackstagePlugin,
coreExtensionData,
} from '@backstage/frontend-plugin-api';
import { RouteExtension } from './extensions/RouteExtension';
import {
createExtensionInstance,
ExtensionInstance,
} from './createExtensionInstance';
// Since we'll never merge arrays in config the config reader context
// isn't too much of a help. Fall back to manual config reading logic
// as the Config interface makes it quite hard for us otherwise.
function readAppExtensionConfigs(
rootConfig: Config,
): Partial<ExtensionInstanceConfig>[] {
const arr = rootConfig.getOptional('app.extensions');
if (!Array.isArray(arr)) {
if (arr === undefined) {
return [];
}
// This will throw, and show which part of config had the wrong type
rootConfig.getConfigArray('app.extensions');
return [];
}
return arr.map((value, index) => {
function errorMsg(msg: string, key?: string, prop?: string) {
return `Invalid extension configuration at app.extensions[${index}]${
key ? `[${key}]` : ''
}${prop ? `.${prop}` : ''}, ${msg}`;
}
if (typeof value === 'string') {
return { id: value };
} else if (
typeof value !== 'object' ||
value === null ||
Array.isArray(value)
) {
throw new Error(errorMsg('must be a string or an object'));
}
const keys = Object.keys(value);
if (keys.length !== 1) {
const joinedKeys = `"${keys.join('", "')}"`;
throw new Error(errorMsg(`must have exactly one key, got ${joinedKeys}`));
}
const key = keys[0];
const obj = value[key];
if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
throw new Error(errorMsg('must be an object', key));
}
const at = obj.at;
if (at !== undefined && typeof at !== 'string') {
throw new Error(errorMsg('must be a string', key, 'at'));
}
const extension = obj.extension;
if (extension !== undefined && typeof extension !== 'string') {
throw new Error(errorMsg('must be a string', key, 'extension'));
}
if (extension) {
throw new Error('TODO: implement extension resolution');
}
return { id: key, at, config: obj.config /* validate later */ };
});
}
/** @public */
export function createApp(options: { plugins: BackstagePlugin[] }): {
createRoot(): JSX.Element;
} {
const appConfig = ConfigReader.fromConfigs(process.env.APP_CONFIG as any);
// pull in default extension instance from discovered packages
// apply config to adjust default extension instances and add more
const extensionInstanceConfigs = [
...options.plugins.flatMap(plugin => plugin.defaultExtensionInstances),
{
id: 'core.router',
at: 'root/default',
extension: RouteExtension,
config: undefined,
},
];
const appExtensionConfigs = readAppExtensionConfigs(appConfig);
for (const appExtensionConfig of appExtensionConfigs) {
const existingConfig = extensionInstanceConfigs.find(
e => e.id === appExtensionConfig.id,
);
if (existingConfig) {
if (appExtensionConfig.at) {
existingConfig.at = appExtensionConfig.at;
}
if (appExtensionConfig.extension) {
// TODO: do we want to reset config here? it might be completely
// unrelated to the previous one
existingConfig.extension = appExtensionConfig.extension;
}
if (appExtensionConfig.config) {
// TODO: merge config?
existingConfig.config = appExtensionConfig.config;
}
} else if (appExtensionConfig.id) {
const { id, at, extension, config } = appExtensionConfig;
if (!at || !extension) {
throw new Error(`Extension ${appExtensionConfig.id} is incomplete`);
}
extensionInstanceConfigs.push({ id, at, extension, config });
}
}
// Create attachment map so that we can look attachments up during instance creation
const attachmentMap = new Map<
string,
Map<string, ExtensionInstanceConfig[]>
>();
for (const instanceConfig of extensionInstanceConfigs) {
const [extensionId, pointId = 'default'] = instanceConfig.at.split('/');
let pointMap = attachmentMap.get(extensionId);
if (!pointMap) {
pointMap = new Map();
attachmentMap.set(extensionId, pointMap);
}
let instances = pointMap.get(pointId);
if (!instances) {
instances = [];
pointMap.set(pointId, instances);
}
instances.push(instanceConfig);
}
const instances = new Map<string, ExtensionInstance>();
function createInstance(
instanceConfig: ExtensionInstanceConfig,
): ExtensionInstance {
const existingInstance = instances.get(instanceConfig.id);
if (existingInstance) {
return existingInstance;
}
const attachments = Object.fromEntries(
Array.from(attachmentMap.get(instanceConfig.id)?.entries() ?? []).map(
([inputName, attachmentConfigs]) => [
inputName,
attachmentConfigs.map(createInstance),
],
),
);
return createExtensionInstance({
id: instanceConfig.id,
config: instanceConfig.config,
extension: instanceConfig.extension,
attachments,
});
}
const rootConfigs = attachmentMap.get('root')?.get('default') ?? [];
const rootInstances = rootConfigs.map(instanceConfig =>
createInstance(instanceConfig),
);
return {
createRoot() {
const rootComponents = rootInstances.map(
e =>
e.data.get(
coreExtensionData.reactComponent.id,
) as typeof coreExtensionData.reactComponent.T,
);
return (
<>
{rootComponents.map(Component => (
<Component />
))}
</>
);
},
};
}
@@ -0,0 +1,52 @@
/*
* 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 { Extension } from '@backstage/frontend-plugin-api';
import mapValues from 'lodash/mapValues';
/** @internal */
export interface ExtensionInstance {
id: string;
data: Map<string, unknown>;
$$type: 'extension-instance';
}
/** @internal */
export function createExtensionInstance(options: {
id: string;
extension: Extension;
config: unknown;
attachments: Record<string, ExtensionInstance[]>;
}): ExtensionInstance {
const { extension, config, attachments } = options;
const extensionData = new Map<string, unknown>();
extension.factory({
config,
bind: mapValues(extension.output, ref => {
return (value: unknown) => extensionData.set(ref.id, value);
}),
inputs: mapValues(
extension.inputs,
({ extensionData: pointData }, inputName) => {
// TODO: validation
return (attachments[inputName] ?? []).map(attachment =>
mapValues(pointData, ref => attachment.data.get(ref.id)),
);
},
),
});
return { id: options.id, data: extensionData, $$type: 'extension-instance' };
}
@@ -0,0 +1,53 @@
/*
* 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 React from 'react';
import {
createExtension,
coreExtensionData,
} from '@backstage/frontend-plugin-api';
import { BrowserRouter, useRoutes } from 'react-router-dom';
export const RouteExtension = createExtension({
inputs: {
routes: {
extensionData: {
path: coreExtensionData.routePath,
component: coreExtensionData.reactComponent,
},
},
},
output: {
component: coreExtensionData.reactComponent,
},
factory({ bind, inputs }) {
const Routes = () => {
const element = useRoutes(
inputs.routes.map(route => ({
path: route.path,
element: <route.component />,
})),
);
return element;
};
bind.component(() => (
<BrowserRouter>
<Routes />
</BrowserRouter>
));
},
});
+23
View File
@@ -0,0 +1,23 @@
/*
* 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.
*/
/**
* TODO
*
* @packageDocumentation
*/
export { createApp } from './createApp';
@@ -0,0 +1,17 @@
/*
* 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 '@testing-library/jest-dom';
@@ -0,0 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
+12
View File
@@ -0,0 +1,12 @@
# @backstage/frontend-plugin-api
_This package was created through the Backstage CLI_.
## Installation
Install the package via Yarn:
```sh
cd <package-dir> # if within a monorepo
yarn add @backstage/frontend-plugin-api
```
+124
View File
@@ -0,0 +1,124 @@
## API Report File for "@backstage/frontend-plugin-api"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { ComponentType } from 'react';
// @public (undocumented)
export type AnyExtensionDataMap = Record<string, ExtensionDataRef<any>>;
// @public (undocumented)
export interface BackstagePlugin {
// (undocumented)
$$type: 'backstage-plugin';
// (undocumented)
defaultExtensionInstances: ExtensionInstanceConfig[];
// (undocumented)
id: string;
}
// @public (undocumented)
export interface BackstagePluginOptions {
// (undocumented)
defaultExtensionInstances?: ExtensionInstanceConfig[];
// (undocumented)
id: string;
}
// @public (undocumented)
export const coreExtensionData: {
reactComponent: ExtensionDataRef<ComponentType<{}>>;
routePath: ExtensionDataRef<string>;
};
// @public (undocumented)
export function createExtension<
TData extends AnyExtensionDataMap,
TPoint extends Record<
string,
{
extensionData: AnyExtensionDataMap;
}
>,
>(options: CreateExtensionOptions<TData, TPoint>): Extension;
// @public (undocumented)
export interface CreateExtensionOptions<
TData extends AnyExtensionDataMap,
TPoint extends Record<
string,
{
extensionData: AnyExtensionDataMap;
}
>,
> {
// (undocumented)
factory(options: {
bind: ExtensionDataBind<TData>;
config?: unknown;
inputs: {
[pointName in keyof TPoint]: ExtensionDataValue<
TPoint[pointName]['extensionData']
>[];
};
}): void;
// (undocumented)
inputs?: TPoint;
// (undocumented)
output: TData;
}
// @public (undocumented)
export function createPlugin(options: BackstagePluginOptions): BackstagePlugin;
// @public (undocumented)
export interface Extension {
// (undocumented)
$$type: 'extension';
// (undocumented)
factory(options: {
bind: ExtensionDataBind<AnyExtensionDataMap>;
config?: unknown;
inputs: Record<string, Array<Record<string, unknown>>>;
}): void;
// (undocumented)
inputs: Record<
string,
{
extensionData: AnyExtensionDataMap;
}
>;
// (undocumented)
output: AnyExtensionDataMap;
}
// @public (undocumented)
export type ExtensionDataBind<TData extends AnyExtensionDataMap> = {
[K in keyof TData]: (value: TData[K]['T']) => void;
};
// @public (undocumented)
export type ExtensionDataRef<T> = {
id: string;
T: T;
$$type: 'extension-data';
};
// @public (undocumented)
export type ExtensionDataValue<TData extends AnyExtensionDataMap> = {
[K in keyof TData]: TData[K]['T'];
};
// @public (undocumented)
export interface ExtensionInstanceConfig {
// (undocumented)
at: string;
// (undocumented)
config: unknown;
// (undocumented)
extension: Extension;
// (undocumented)
id: string;
}
```
+38
View File
@@ -0,0 +1,38 @@
{
"name": "@backstage/frontend-plugin-api",
"version": "0.0.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"private": true,
"publishConfig": {
"access": "public",
"main": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"backstage": {
"role": "web-library"
},
"scripts": {
"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",
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack"
},
"devDependencies": {
"@backstage/cli": "workspace:^",
"@testing-library/jest-dom": "^5.10.1"
},
"files": [
"dist"
],
"peerDependencies": {
"react": "^16.13.1 || ^17.0.0"
},
"dependencies": {
"lodash": "^4.17.21"
}
}
+36
View File
@@ -0,0 +1,36 @@
/*
* 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.
*/
/**
* TODO
*
* @packageDocumentation
*/
export {
createExtension,
coreExtensionData,
createPlugin,
type ExtensionInstanceConfig,
type BackstagePlugin,
type Extension,
type AnyExtensionDataMap,
type BackstagePluginOptions,
type CreateExtensionOptions,
type ExtensionDataBind,
type ExtensionDataRef,
type ExtensionDataValue,
} from './types';
@@ -0,0 +1,17 @@
/*
* 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 '@testing-library/jest-dom';
+116
View File
@@ -0,0 +1,116 @@
/*
* 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 { ComponentType } from 'react';
/** @public */
export type ExtensionDataRef<T> = {
id: string;
T: T;
$$type: 'extension-data';
};
/** @public */
export function createExtensionDataRef<T>(id: string): ExtensionDataRef<T> {
return { id, $$type: 'extension-data' } as ExtensionDataRef<T>;
}
/** @public */
export const coreExtensionData = {
reactComponent: createExtensionDataRef<ComponentType>('core.reactComponent'),
routePath: createExtensionDataRef<string>('core.routing.path'),
};
/** @public */
export type AnyExtensionDataMap = Record<string, ExtensionDataRef<any>>;
/** @public */
export type ExtensionDataBind<TData extends AnyExtensionDataMap> = {
[K in keyof TData]: (value: TData[K]['T']) => void;
};
/** @public */
export type ExtensionDataValue<TData extends AnyExtensionDataMap> = {
[K in keyof TData]: TData[K]['T'];
};
/** @public */
export interface CreateExtensionOptions<
TData extends AnyExtensionDataMap,
TPoint extends Record<string, { extensionData: AnyExtensionDataMap }>,
> {
inputs?: TPoint;
output: TData;
factory(options: {
bind: ExtensionDataBind<TData>;
config?: unknown;
inputs: {
[pointName in keyof TPoint]: ExtensionDataValue<
TPoint[pointName]['extensionData']
>[];
};
}): void;
}
/** @public */
export interface Extension {
$$type: 'extension';
inputs: Record<string, { extensionData: AnyExtensionDataMap }>;
output: AnyExtensionDataMap;
factory(options: {
bind: ExtensionDataBind<AnyExtensionDataMap>;
config?: unknown;
inputs: Record<string, Array<Record<string, unknown>>>;
}): void;
}
/** @public */
export function createExtension<
TData extends AnyExtensionDataMap,
TPoint extends Record<string, { extensionData: AnyExtensionDataMap }>,
>(options: CreateExtensionOptions<TData, TPoint>): Extension {
return { ...options, $$type: 'extension', inputs: options.inputs ?? {} };
}
/** @public */
export interface ExtensionInstanceConfig {
id: string;
at: string;
extension: Extension;
config: unknown;
}
/** @public */
export interface BackstagePluginOptions {
id: string;
defaultExtensionInstances?: ExtensionInstanceConfig[];
}
/** @public */
export interface BackstagePlugin {
$$type: 'backstage-plugin';
id: string;
defaultExtensionInstances: ExtensionInstanceConfig[];
}
/** @public */
export function createPlugin(options: BackstagePluginOptions): BackstagePlugin {
return {
...options,
$$type: 'backstage-plugin',
defaultExtensionInstances: options.defaultExtensionInstances ?? [],
};
}
+138
View File
@@ -4132,6 +4132,34 @@ __metadata:
languageName: unknown
linkType: soft
"@backstage/frontend-app-api@workspace:^, @backstage/frontend-app-api@workspace:packages/frontend-app-api":
version: 0.0.0-use.local
resolution: "@backstage/frontend-app-api@workspace:packages/frontend-app-api"
dependencies:
"@backstage/cli": "workspace:^"
"@backstage/config": "workspace:^"
"@backstage/frontend-plugin-api": "workspace:^"
"@backstage/plugin-graphiql": "workspace:^"
"@testing-library/jest-dom": ^5.10.1
lodash: ^4.17.21
peerDependencies:
react: ^16.13.1 || ^17.0.0
react-router-dom: 6.0.0-beta.0 || ^6.3.0
languageName: unknown
linkType: soft
"@backstage/frontend-plugin-api@workspace:^, @backstage/frontend-plugin-api@workspace:packages/frontend-plugin-api":
version: 0.0.0-use.local
resolution: "@backstage/frontend-plugin-api@workspace:packages/frontend-plugin-api"
dependencies:
"@backstage/cli": "workspace:^"
"@testing-library/jest-dom": ^5.10.1
lodash: ^4.17.21
peerDependencies:
react: ^16.13.1 || ^17.0.0
languageName: unknown
linkType: soft
"@backstage/integration-aws-node@workspace:^, @backstage/integration-aws-node@workspace:packages/integration-aws-node":
version: 0.0.0-use.local
resolution: "@backstage/integration-aws-node@workspace:packages/integration-aws-node"
@@ -25316,6 +25344,116 @@ __metadata:
languageName: node
linkType: hard
"example-app-next@workspace:packages/app-next":
version: 0.0.0-use.local
resolution: "example-app-next@workspace:packages/app-next"
dependencies:
"@backstage/app-defaults": "workspace:^"
"@backstage/catalog-model": "workspace:^"
"@backstage/cli": "workspace:^"
"@backstage/config": "workspace:^"
"@backstage/core-app-api": "workspace:^"
"@backstage/core-components": "workspace:^"
"@backstage/core-plugin-api": "workspace:^"
"@backstage/frontend-app-api": "workspace:^"
"@backstage/frontend-plugin-api": "workspace:^"
"@backstage/integration-react": "workspace:^"
"@backstage/plugin-adr": "workspace:^"
"@backstage/plugin-airbrake": "workspace:^"
"@backstage/plugin-apache-airflow": "workspace:^"
"@backstage/plugin-api-docs": "workspace:^"
"@backstage/plugin-azure-devops": "workspace:^"
"@backstage/plugin-azure-sites": "workspace:^"
"@backstage/plugin-badges": "workspace:^"
"@backstage/plugin-catalog-common": "workspace:^"
"@backstage/plugin-catalog-graph": "workspace:^"
"@backstage/plugin-catalog-import": "workspace:^"
"@backstage/plugin-catalog-react": "workspace:^"
"@backstage/plugin-catalog-unprocessed-entities": "workspace:^"
"@backstage/plugin-circleci": "workspace:^"
"@backstage/plugin-cloudbuild": "workspace:^"
"@backstage/plugin-code-coverage": "workspace:^"
"@backstage/plugin-cost-insights": "workspace:^"
"@backstage/plugin-devtools": "workspace:^"
"@backstage/plugin-dynatrace": "workspace:^"
"@backstage/plugin-entity-feedback": "workspace:^"
"@backstage/plugin-explore": "workspace:^"
"@backstage/plugin-gcalendar": "workspace:^"
"@backstage/plugin-gcp-projects": "workspace:^"
"@backstage/plugin-github-actions": "workspace:^"
"@backstage/plugin-gocd": "workspace:^"
"@backstage/plugin-graphiql": "workspace:^"
"@backstage/plugin-home": "workspace:^"
"@backstage/plugin-jenkins": "workspace:^"
"@backstage/plugin-kafka": "workspace:^"
"@backstage/plugin-kubernetes": "workspace:^"
"@backstage/plugin-lighthouse": "workspace:^"
"@backstage/plugin-linguist": "workspace:^"
"@backstage/plugin-linguist-common": "workspace:^"
"@backstage/plugin-microsoft-calendar": "workspace:^"
"@backstage/plugin-newrelic": "workspace:^"
"@backstage/plugin-newrelic-dashboard": "workspace:^"
"@backstage/plugin-octopus-deploy": "workspace:^"
"@backstage/plugin-org": "workspace:^"
"@backstage/plugin-pagerduty": "workspace:^"
"@backstage/plugin-permission-react": "workspace:^"
"@backstage/plugin-playlist": "workspace:^"
"@backstage/plugin-puppetdb": "workspace:^"
"@backstage/plugin-rollbar": "workspace:^"
"@backstage/plugin-scaffolder": "workspace:^"
"@backstage/plugin-scaffolder-react": "workspace:^"
"@backstage/plugin-search": "workspace:^"
"@backstage/plugin-search-common": "workspace:^"
"@backstage/plugin-search-react": "workspace:^"
"@backstage/plugin-sentry": "workspace:^"
"@backstage/plugin-shortcuts": "workspace:^"
"@backstage/plugin-stack-overflow": "workspace:^"
"@backstage/plugin-stackstorm": "workspace:^"
"@backstage/plugin-tech-insights": "workspace:^"
"@backstage/plugin-tech-radar": "workspace:^"
"@backstage/plugin-techdocs": "workspace:^"
"@backstage/plugin-techdocs-module-addons-contrib": "workspace:^"
"@backstage/plugin-techdocs-react": "workspace:^"
"@backstage/plugin-todo": "workspace:^"
"@backstage/plugin-user-settings": "workspace:^"
"@backstage/test-utils": "workspace:^"
"@backstage/theme": "workspace:^"
"@internal/plugin-catalog-customized": "workspace:^"
"@material-ui/core": ^4.12.2
"@material-ui/icons": ^4.9.1
"@material-ui/lab": 4.0.0-alpha.61
"@octokit/rest": ^19.0.3
"@oriflame/backstage-plugin-score-card": ^0.7.0
"@roadiehq/backstage-plugin-buildkite": ^2.0.8
"@roadiehq/backstage-plugin-github-insights": ^2.0.5
"@roadiehq/backstage-plugin-github-pull-requests": ^2.2.7
"@roadiehq/backstage-plugin-travis-ci": ^2.0.5
"@testing-library/cypress": ^9.0.0
"@testing-library/dom": ^8.0.0
"@testing-library/jest-dom": ^5.10.1
"@testing-library/react": ^12.1.3
"@testing-library/user-event": ^14.0.0
"@types/jquery": ^3.3.34
"@types/node": ^16.11.26
"@types/react": "*"
"@types/react-dom": "*"
"@types/zen-observable": ^0.8.0
cross-env: ^7.0.0
cypress: ^10.0.0
eslint-plugin-cypress: ^2.10.3
history: ^5.0.0
lodash: ^4.17.21
prop-types: ^15.7.2
react: ^17.0.2
react-dom: ^17.0.2
react-router: ^6.3.0
react-router-dom: ^6.3.0
react-use: ^17.2.4
start-server-and-test: ^1.10.11
zen-observable: ^0.10.0
languageName: unknown
linkType: soft
"example-app@link:../app::locator=example-backend%40workspace%3Apackages%2Fbackend":
version: 0.0.0-use.local
resolution: "example-app@link:../app::locator=example-backend%40workspace%3Apackages%2Fbackend"