diff --git a/packages/app-next/.eslintrc.js b/packages/app-next/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/packages/app-next/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/packages/app-next/README.md b/packages/app-next/README.md new file mode 100644 index 0000000000..21645609a9 --- /dev/null +++ b/packages/app-next/README.md @@ -0,0 +1,3 @@ +# example-app-next + +This package is an example of a Backstage application. diff --git a/packages/app-next/app-config.yaml b/packages/app-next/app-config.yaml new file mode 100644 index 0000000000..463113fd55 --- /dev/null +++ b/packages/app-next/app-config.yaml @@ -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 diff --git a/packages/app-next/architecture.drawio.svg b/packages/app-next/architecture.drawio.svg new file mode 100644 index 0000000000..b5ed4de71a --- /dev/null +++ b/packages/app-next/architecture.drawio.svg @@ -0,0 +1,614 @@ + + + + + + + + + + + + + + + + + + +
+
+
+ Extension Instance +
+
+
+
+ + Extension Ins... + +
+
+ + + + + + + +
+
+
+ Extension +
+
+
+
+ + Extension + +
+
+ + + + +
+
+
+ Output +
+
+
+
+ + Output + +
+
+ + + + + +
+
+
+ Input 1 +
+
+
+
+ + Input 1 + +
+
+ + + + + +
+
+
+ Input 2 +
+
+
+
+ + Input 2 + +
+
+ + + + + +
+
+
+ React Component +
+
+
+
+ + React Component + +
+
+ + + + + +
+
+
+ Route Path +
+
+
+
+ + Route Path + +
+
+ + + + + +
+
+
+ Display Title +
+
+
+
+ + Display Title + +
+
+ + + + + + +
+
+
+ Configuration +
+
+
+
+ + Configuration + +
+
+ + + + + + +
+
+
+ Parent +
+
+
+
+ + Parent + +
+
+ + + + + +
+
+
+ + Configuration Schema + +
+
+
+
+ + Configuration Sc... + +
+
+ + + + +
+
+
+ ID +
+
+
+
+ + ID + +
+
+ + + + + +
+
+
+ Multi Input Attachments +
+
+
+
+ + Multi Input Attachments + +
+
+ + + + +
+
+
+ Child +
+
+
+
+ + Child + +
+
+ + + + + + +
+
+
+ Child +
+
+
+
+ + Child + +
+
+ + + + + + + + + + + + + + +
+
+
+ Single Input Attachment +
+
+
+
+ + Single Input Attachm... + +
+
+ + + + +
+
+
+ Child +
+
+
+
+ + Child + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ Other +
+
+
+
+ + Other + +
+
+ + + + + + + + + + + + + + + +
+
+
+ Output Data +
+
+
+
+ + Output Data + +
+
+ + + + + +
+
+
+ Input Data +
+
+
+
+ + Input Data + +
+
+ + + + + + + + + +
+
+
+ Root +
+
+
+
+ + Root + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + Example Extension Data Types + +
+
+
+
+ + Example Extension Data Types + +
+
+ + + + +
+
+
+ Extension Architecture +
+
+
+
+ + Extension Architecture + +
+
+ + + + +
+
+
+ App Example +
+
+
+
+ + App Example + +
+
+ + +
+ + + + + Text is not SVG - cannot display + + + +
diff --git a/packages/app-next/package.json b/packages/app-next/package.json new file mode 100644 index 0000000000..d2642c2fde --- /dev/null +++ b/packages/app-next/package.json @@ -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" +} diff --git a/packages/app-next/public/android-chrome-192x192.png b/packages/app-next/public/android-chrome-192x192.png new file mode 100644 index 0000000000..eec0ae25b9 Binary files /dev/null and b/packages/app-next/public/android-chrome-192x192.png differ diff --git a/packages/app-next/public/android-chrome-512x512.png b/packages/app-next/public/android-chrome-512x512.png new file mode 100644 index 0000000000..ac0578133b Binary files /dev/null and b/packages/app-next/public/android-chrome-512x512.png differ diff --git a/packages/app-next/public/apple-touch-icon.png b/packages/app-next/public/apple-touch-icon.png new file mode 100644 index 0000000000..6a45afb38f Binary files /dev/null and b/packages/app-next/public/apple-touch-icon.png differ diff --git a/packages/app-next/public/favicon-16x16.png b/packages/app-next/public/favicon-16x16.png new file mode 100644 index 0000000000..58cf61a35e Binary files /dev/null and b/packages/app-next/public/favicon-16x16.png differ diff --git a/packages/app-next/public/favicon-32x32.png b/packages/app-next/public/favicon-32x32.png new file mode 100644 index 0000000000..c0915ece75 Binary files /dev/null and b/packages/app-next/public/favicon-32x32.png differ diff --git a/packages/app-next/public/favicon.ico b/packages/app-next/public/favicon.ico new file mode 100644 index 0000000000..5e45e5dfbd Binary files /dev/null and b/packages/app-next/public/favicon.ico differ diff --git a/packages/app-next/public/favicon.svg b/packages/app-next/public/favicon.svg new file mode 100644 index 0000000000..7f2d375cd8 --- /dev/null +++ b/packages/app-next/public/favicon.svg @@ -0,0 +1 @@ +Backstage favicon \ No newline at end of file diff --git a/packages/app-next/public/index.html b/packages/app-next/public/index.html new file mode 100644 index 0000000000..0710a0d69d --- /dev/null +++ b/packages/app-next/public/index.html @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + <%= config.getString('app.title') %> + + <% if (config.has('app.datadogRum')) { %> + + <% } %> + + + + +
+ + + diff --git a/packages/app-next/public/manifest.json b/packages/app-next/public/manifest.json new file mode 100644 index 0000000000..4a7c1b4ec4 --- /dev/null +++ b/packages/app-next/public/manifest.json @@ -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" +} diff --git a/packages/app-next/public/robots.txt b/packages/app-next/public/robots.txt new file mode 100644 index 0000000000..01b0f9a107 --- /dev/null +++ b/packages/app-next/public/robots.txt @@ -0,0 +1,2 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * diff --git a/packages/app-next/public/safari-pinned-tab.svg b/packages/app-next/public/safari-pinned-tab.svg new file mode 100644 index 0000000000..0f500b3002 --- /dev/null +++ b/packages/app-next/public/safari-pinned-tab.svg @@ -0,0 +1 @@ +Created by potrace 1.11, written by Peter Selinger 2001-2013 \ No newline at end of file diff --git a/packages/app-next/src/App.test.tsx b/packages/app-next/src/App.test.tsx new file mode 100644 index 0000000000..047c9821b5 --- /dev/null +++ b/packages/app-next/src/App.test.tsx @@ -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(); + expect(rendered.baseElement).toBeInTheDocument(); + }); +}); diff --git a/packages/app-next/src/App.tsx b/packages/app-next/src/App.tsx new file mode 100644 index 0000000000..b875fb3927 --- /dev/null +++ b/packages/app-next/src/App.tsx @@ -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 = ( +// +// {/* } /> +// } /> +// } +// > +// +// +// +// +// +// + +// +// +// +// +// + +// +// +// +// +// +// +// } +// /> */} +// {/* } +// /> */} +// } /> +// +// ); + +// export default app.createRoot( +// <> +// {/* +// */} +// {routes} +// , +// ); diff --git a/packages/app-next/src/examples/graphiqlPlugin.tsx b/packages/app-next/src/examples/graphiqlPlugin.tsx new file mode 100644 index 0000000000..87f2bb242c --- /dev/null +++ b/packages/app-next/src/examples/graphiqlPlugin.tsx @@ -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 ; + }); + // 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' }, + }, + ], +}); diff --git a/packages/app-next/src/index.tsx b/packages/app-next/src/index.tsx new file mode 100644 index 0000000000..b15bc4c102 --- /dev/null +++ b/packages/app-next/src/index.tsx @@ -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(, document.getElementById('root')); diff --git a/packages/app-next/src/setupTests.ts b/packages/app-next/src/setupTests.ts new file mode 100644 index 0000000000..23fcbe9676 --- /dev/null +++ b/packages/app-next/src/setupTests.ts @@ -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'; diff --git a/packages/frontend-app-api/.eslintrc.js b/packages/frontend-app-api/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/packages/frontend-app-api/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/packages/frontend-app-api/README.md b/packages/frontend-app-api/README.md new file mode 100644 index 0000000000..5cc5073f61 --- /dev/null +++ b/packages/frontend-app-api/README.md @@ -0,0 +1,12 @@ +# @backstage/frontend-app-api + +_This package was created through the Backstage CLI_. + +## Installation + +Install the package via Yarn: + +```sh +cd # if within a monorepo +yarn add @backstage/frontend-app-api +``` diff --git a/packages/frontend-app-api/api-report.md b/packages/frontend-app-api/api-report.md new file mode 100644 index 0000000000..bf7db4451b --- /dev/null +++ b/packages/frontend-app-api/api-report.md @@ -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 +/// + +import { BackstagePlugin } from '@backstage/frontend-plugin-api'; + +// @public (undocumented) +export function createApp(options: { plugins: BackstagePlugin[] }): { + createRoot(): JSX.Element; +}; +``` diff --git a/packages/frontend-app-api/config.d.ts b/packages/frontend-app-api/config.d.ts new file mode 100644 index 0000000000..81c24d1185 --- /dev/null +++ b/packages/frontend-app-api/config.d.ts @@ -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; + }; + }; + }; +} diff --git a/packages/frontend-app-api/package.json b/packages/frontend-app-api/package.json new file mode 100644 index 0000000000..c085c6dba6 --- /dev/null +++ b/packages/frontend-app-api/package.json @@ -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" + } +} diff --git a/packages/frontend-app-api/src/createApp.tsx b/packages/frontend-app-api/src/createApp.tsx new file mode 100644 index 0000000000..46cd0a7aed --- /dev/null +++ b/packages/frontend-app-api/src/createApp.tsx @@ -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[] { + 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 + >(); + 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(); + + 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 => ( + + ))} + + ); + }, + }; +} diff --git a/packages/frontend-app-api/src/createExtensionInstance.ts b/packages/frontend-app-api/src/createExtensionInstance.ts new file mode 100644 index 0000000000..0b62df74ae --- /dev/null +++ b/packages/frontend-app-api/src/createExtensionInstance.ts @@ -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; + $$type: 'extension-instance'; +} + +/** @internal */ +export function createExtensionInstance(options: { + id: string; + extension: Extension; + config: unknown; + attachments: Record; +}): ExtensionInstance { + const { extension, config, attachments } = options; + const extensionData = new Map(); + 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' }; +} diff --git a/packages/frontend-app-api/src/extensions/RouteExtension.tsx b/packages/frontend-app-api/src/extensions/RouteExtension.tsx new file mode 100644 index 0000000000..ef7e34ea30 --- /dev/null +++ b/packages/frontend-app-api/src/extensions/RouteExtension.tsx @@ -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: , + })), + ); + + return element; + }; + bind.component(() => ( + + + + )); + }, +}); diff --git a/packages/frontend-app-api/src/index.ts b/packages/frontend-app-api/src/index.ts new file mode 100644 index 0000000000..45fe9413ba --- /dev/null +++ b/packages/frontend-app-api/src/index.ts @@ -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'; diff --git a/packages/frontend-app-api/src/setupTests.ts b/packages/frontend-app-api/src/setupTests.ts new file mode 100644 index 0000000000..c30f1d15cb --- /dev/null +++ b/packages/frontend-app-api/src/setupTests.ts @@ -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'; diff --git a/packages/frontend-plugin-api/.eslintrc.js b/packages/frontend-plugin-api/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/packages/frontend-plugin-api/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/packages/frontend-plugin-api/README.md b/packages/frontend-plugin-api/README.md new file mode 100644 index 0000000000..43cb4082dc --- /dev/null +++ b/packages/frontend-plugin-api/README.md @@ -0,0 +1,12 @@ +# @backstage/frontend-plugin-api + +_This package was created through the Backstage CLI_. + +## Installation + +Install the package via Yarn: + +```sh +cd # if within a monorepo +yarn add @backstage/frontend-plugin-api +``` diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md new file mode 100644 index 0000000000..4337b0b8c2 --- /dev/null +++ b/packages/frontend-plugin-api/api-report.md @@ -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>; + +// @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>; + routePath: ExtensionDataRef; +}; + +// @public (undocumented) +export function createExtension< + TData extends AnyExtensionDataMap, + TPoint extends Record< + string, + { + extensionData: AnyExtensionDataMap; + } + >, +>(options: CreateExtensionOptions): Extension; + +// @public (undocumented) +export interface CreateExtensionOptions< + TData extends AnyExtensionDataMap, + TPoint extends Record< + string, + { + extensionData: AnyExtensionDataMap; + } + >, +> { + // (undocumented) + factory(options: { + bind: ExtensionDataBind; + 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; + config?: unknown; + inputs: Record>>; + }): void; + // (undocumented) + inputs: Record< + string, + { + extensionData: AnyExtensionDataMap; + } + >; + // (undocumented) + output: AnyExtensionDataMap; +} + +// @public (undocumented) +export type ExtensionDataBind = { + [K in keyof TData]: (value: TData[K]['T']) => void; +}; + +// @public (undocumented) +export type ExtensionDataRef = { + id: string; + T: T; + $$type: 'extension-data'; +}; + +// @public (undocumented) +export type ExtensionDataValue = { + [K in keyof TData]: TData[K]['T']; +}; + +// @public (undocumented) +export interface ExtensionInstanceConfig { + // (undocumented) + at: string; + // (undocumented) + config: unknown; + // (undocumented) + extension: Extension; + // (undocumented) + id: string; +} +``` diff --git a/packages/frontend-plugin-api/package.json b/packages/frontend-plugin-api/package.json new file mode 100644 index 0000000000..731e66fed0 --- /dev/null +++ b/packages/frontend-plugin-api/package.json @@ -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" + } +} diff --git a/packages/frontend-plugin-api/src/index.ts b/packages/frontend-plugin-api/src/index.ts new file mode 100644 index 0000000000..e36391131d --- /dev/null +++ b/packages/frontend-plugin-api/src/index.ts @@ -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'; diff --git a/packages/frontend-plugin-api/src/setupTests.ts b/packages/frontend-plugin-api/src/setupTests.ts new file mode 100644 index 0000000000..c30f1d15cb --- /dev/null +++ b/packages/frontend-plugin-api/src/setupTests.ts @@ -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'; diff --git a/packages/frontend-plugin-api/src/types.ts b/packages/frontend-plugin-api/src/types.ts new file mode 100644 index 0000000000..b2d131c62f --- /dev/null +++ b/packages/frontend-plugin-api/src/types.ts @@ -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 = { + id: string; + T: T; + $$type: 'extension-data'; +}; + +/** @public */ +export function createExtensionDataRef(id: string): ExtensionDataRef { + return { id, $$type: 'extension-data' } as ExtensionDataRef; +} + +/** @public */ +export const coreExtensionData = { + reactComponent: createExtensionDataRef('core.reactComponent'), + routePath: createExtensionDataRef('core.routing.path'), +}; + +/** @public */ +export type AnyExtensionDataMap = Record>; + +/** @public */ +export type ExtensionDataBind = { + [K in keyof TData]: (value: TData[K]['T']) => void; +}; + +/** @public */ +export type ExtensionDataValue = { + [K in keyof TData]: TData[K]['T']; +}; + +/** @public */ +export interface CreateExtensionOptions< + TData extends AnyExtensionDataMap, + TPoint extends Record, +> { + inputs?: TPoint; + output: TData; + factory(options: { + bind: ExtensionDataBind; + config?: unknown; + inputs: { + [pointName in keyof TPoint]: ExtensionDataValue< + TPoint[pointName]['extensionData'] + >[]; + }; + }): void; +} + +/** @public */ +export interface Extension { + $$type: 'extension'; + inputs: Record; + output: AnyExtensionDataMap; + factory(options: { + bind: ExtensionDataBind; + config?: unknown; + inputs: Record>>; + }): void; +} + +/** @public */ +export function createExtension< + TData extends AnyExtensionDataMap, + TPoint extends Record, +>(options: CreateExtensionOptions): 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 ?? [], + }; +} diff --git a/yarn.lock b/yarn.lock index 2e780973d7..05a48457c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"