Merge branch 'master' into ebarriosjr/Creating-gcp-projects-plugin
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: 'Feature Request'
|
||||
about: 'Suggest new features and changes'
|
||||
labels: help wanted
|
||||
labels: enhancement
|
||||
---
|
||||
|
||||
<!--- Provide a general summary of the feature request in the Title above -->
|
||||
|
||||
@@ -74,3 +74,11 @@ jobs:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
package_root: "packages/core"
|
||||
tag_prefix: "v"
|
||||
|
||||
- name: Discord notification
|
||||
if: ${{ failure() }}
|
||||
uses: Ilshidur/action-discord@0.2.0
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
with:
|
||||
args: 'Master build failed https://github.com/{{GITHUB_REPOSITORY}}/actions/runs/{{GITHUB_RUN_ID}}'
|
||||
|
||||
@@ -92,6 +92,7 @@ dist
|
||||
|
||||
# Microsite build output
|
||||
microsite/build
|
||||
microsite/i18n
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
|
||||
@@ -12,3 +12,4 @@
|
||||
| [Voi](https://www.voiscooters.com/) | [@K-Phoen](https://github.com/K-Phoen) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. |
|
||||
| [Talkdesk](https://www.talkdesk.com) | [@jaime-talkdesk](https://github.com/jaime-talkdesk) | Initial work for Engineering Portal and Self Provisioning to R&D |
|
||||
| [Wealthsimple](https://www.wealthsimple.com) | [@andrewthauer](https://github.com/andrewthauer) | Developer portal, service catalog, documentation and tooling |
|
||||
| [Grab](https://www.grab.com) | [@althafh](https://github.com/althafh) | Initial work as a unified interface for all of Grab's internal tooling |
|
||||
|
||||
@@ -8,6 +8,15 @@ If you encounter issues while upgrading to a newer version, don't hesitate to re
|
||||
|
||||
> Collect changes for the next release below
|
||||
|
||||
- The backend plugin
|
||||
[service builder](https://github.com/spotify/backstage/blob/master/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts)
|
||||
no longer adds `express.json()` automatically to all routes. While convenient
|
||||
in a lot of cases, it also led to problems where for example the proxy
|
||||
middleware could hang because the body had already been altered and could not
|
||||
be streamed. Also, plugins that rather wanted to handle e.g. form encoded data
|
||||
still had to cater to that manually. We therefore decided to let plugins add
|
||||
`express.json()` themselves if they happen to deal with JSON data.
|
||||
|
||||
## v0.1.1-alpha.20
|
||||
|
||||
- Includes https://github.com/spotify/backstage/pull/2097 to resolve issues with create-plugin command.
|
||||
|
||||
+2
-5
@@ -1,7 +1,4 @@
|
||||
---
|
||||
id: CONTRIBUTING
|
||||
title: Contributing
|
||||
---
|
||||
# Contributing to Backstage
|
||||
|
||||
Our vision for Backstage is for it to become the trusted standard toolbox (read: UX layer) for the open source infrastructure landscape. Think of it like Kubernetes for developer experience. We realize this is an ambitious goal. We can’t do it alone.
|
||||
|
||||
@@ -31,7 +28,7 @@ What kind of plugins should/could be created? Some inspiration from the 120+ plu
|
||||
|
||||
## Suggesting a plugin
|
||||
|
||||
If you start developing a plugin that you aim to release as open source, we suggest that you create a new [new Issue](https://github.com/spotify/backstage/issues/new?template=plugin_template.md). This helps the community know what plugins are in development.
|
||||
If you start developing a plugin that you aim to release as open source, we suggest that you create a new [new Issue](https://github.com/spotify/backstage/issues/new?labels=plugin&template=plugin_template.md&title=%5BPlugin%5D+THE+PLUGIN+NAME). This helps the community know what plugins are in development.
|
||||
|
||||
You can also use this process if you have an idea for a good plugin but you hope that someone else will pick up the work.
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ Take a look at the [Getting Started](https://backstage.io/docs/getting-started/i
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Main documentation](https://backstage.io/docs/overview/what-is-backstage)
|
||||
- [Main documentation](https://backstage.io/docs)
|
||||
- [Service Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview)
|
||||
- [Architecture](https://backstage.io/docs/overview/architecture-terminology) ([Decisions](https://backstage.io/docs/architecture-decisions/adrs-overview))
|
||||
- [Designing for Backstage](https://backstage.io/docs/dls/design)
|
||||
|
||||
+30
-9
@@ -14,21 +14,15 @@ backend:
|
||||
client: sqlite3
|
||||
connection: ':memory:'
|
||||
|
||||
# See README.md in the proxy-backend plugin for information on the configuration format
|
||||
proxy:
|
||||
'/circleci/api':
|
||||
target: 'https://circleci.com/api/v1.1'
|
||||
changeOrigin: true
|
||||
pathRewrite:
|
||||
'^/proxy/circleci/api/': '/'
|
||||
'/circleci/api': https://circleci.com/api/v1.1
|
||||
'/jenkins/api':
|
||||
target: 'http://localhost:8080'
|
||||
changeOrigin: true
|
||||
target: http://localhost:8080
|
||||
headers:
|
||||
Authorization:
|
||||
$secret:
|
||||
env: JENKINS_BASIC_AUTH_HEADER
|
||||
pathRewrite:
|
||||
'^/proxy/jenkins/api/': '/'
|
||||
|
||||
organization:
|
||||
name: Spotify
|
||||
@@ -50,7 +44,32 @@ newrelic:
|
||||
baseUrl: 'https://api.newrelic.com/v2'
|
||||
key: NEW_RELIC_REST_API_KEY
|
||||
|
||||
lighthouse:
|
||||
baseUrl: http://localhost:3003
|
||||
|
||||
catalog:
|
||||
rules:
|
||||
- allow: [Component, API, Group, Template, Location]
|
||||
processors:
|
||||
githubApi:
|
||||
privateToken:
|
||||
$secret:
|
||||
env: GITHUB_PRIVATE_TOKEN
|
||||
bitbucketApi:
|
||||
username:
|
||||
$secret:
|
||||
env: BITBUCKET_USERNAME
|
||||
appPassword:
|
||||
$secret:
|
||||
env: BITBUCKET_APP_PASSWORD
|
||||
gitlabApi:
|
||||
privateToken:
|
||||
$secret:
|
||||
env: GITLAB_PRIVATE_TOKEN
|
||||
azureApi:
|
||||
privateToken:
|
||||
$secret:
|
||||
env: AZURE_PRIVATE_TOKEN
|
||||
exampleEntityLocations:
|
||||
github:
|
||||
- https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/artist-lookup-component.yaml
|
||||
@@ -66,6 +85,8 @@ catalog:
|
||||
- https://github.com/spotify/backstage/blob/master/plugins/scaffolder-backend/sample-templates/create-react-app/template.yaml
|
||||
- https://github.com/spotify/cookiecutter-golang/blob/master/template.yaml
|
||||
- https://github.com/spotify/backstage/blob/master/plugins/scaffolder-backend/sample-templates/docs-template/template.yaml
|
||||
- https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/hello-world-api.yaml
|
||||
- https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/streetlights-api.yaml
|
||||
|
||||
auth:
|
||||
providers:
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
id: software-catalog-configuration
|
||||
title: Catalog Configuration
|
||||
---
|
||||
|
||||
## Static Location Configuration
|
||||
|
||||
To enable declarative catalog setups, it is possible to add locations to the
|
||||
catalog via [static configuration](../../conf/index.md). Locations are added to
|
||||
the catalog under the `catalog.locations` key, for example:
|
||||
|
||||
```yaml
|
||||
catalog:
|
||||
locations:
|
||||
- type: github
|
||||
target: https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/artist-lookup-component.yaml
|
||||
```
|
||||
|
||||
The locations added through static configuration can not be removed through the
|
||||
catalog locations API. To remove the locations, you have to remove them from the
|
||||
configuration.
|
||||
|
||||
## Catalog Rules
|
||||
|
||||
By default the catalog will only allow ingestion of entities with the kind
|
||||
`Component`, `API` and `Location`. In order to allow entities of other kinds to
|
||||
be added, you need to add rules to the catalog. Rules are added either in a
|
||||
separate `catalog.rules` key, or added to statically configured locations.
|
||||
|
||||
For example, given the following configuration:
|
||||
|
||||
```yaml
|
||||
catalog:
|
||||
rules:
|
||||
- allow: [Component, API, Location, Template]
|
||||
|
||||
locations:
|
||||
- type: github
|
||||
target: https://github.com/org/example/blob/master/org-data.yaml
|
||||
rules:
|
||||
- allow: [Group]
|
||||
```
|
||||
|
||||
We are able to add entities of kind `Component`, `API`, `Location`, or
|
||||
`Template` from any location, and `Group` entities from the `org-data.yaml`,
|
||||
which will also be read as statically configured location.
|
||||
|
||||
Note that if the `catalog.rules` key is present it will replace the default
|
||||
value, meaning that you need to add rules for the default kinds if you want
|
||||
those to still be allowed.
|
||||
|
||||
The following configuration will reject any kind of entities from being added to
|
||||
the catalog:
|
||||
|
||||
```yaml
|
||||
catalog:
|
||||
rules: []
|
||||
```
|
||||
@@ -79,6 +79,22 @@ All software created through the
|
||||
[Backstage Software Templates](../software-templates/index.md) are automatically
|
||||
registered in the catalog.
|
||||
|
||||
### Static catalog configuration
|
||||
|
||||
In addition to manually registering components, it is also possible to register
|
||||
components though [static configuration](../../conf/index.md). For example, the
|
||||
above example can be added using the following configuration:
|
||||
|
||||
```yaml
|
||||
catalog:
|
||||
locations:
|
||||
- type: github
|
||||
target: https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/artist-lookup-component.yaml
|
||||
```
|
||||
|
||||
More information about catalog configuration can be found
|
||||
[here](configuration.md).
|
||||
|
||||
### Updating component metadata
|
||||
|
||||
Teams owning the components are responsible for maintaining the metadata about
|
||||
|
||||
@@ -58,7 +58,30 @@ Currently the catalog supports loading definitions from GitHub + Local Files. To
|
||||
load from other places, not only will there need to be another preparer, but the
|
||||
support to load the location will also need to be added to the Catalog.
|
||||
|
||||
For loading from a file the following command should work when the backend is
|
||||
You can add the template files to the catalog through
|
||||
[static location configuration](../software-catalog/configuration.md#static-location-configuration),
|
||||
for example
|
||||
|
||||
```yaml
|
||||
catalog:
|
||||
locations:
|
||||
- type: github
|
||||
target: https://github.com/spotify/cookiecutter-golang/blob/master/template.yaml
|
||||
rules:
|
||||
- allow: [Template]
|
||||
```
|
||||
|
||||
Templates can also be added by posting the to the catalog directly. Note that if
|
||||
you're doing this, you need to configure the catalog to allow template entities
|
||||
to be ingested from any source, for example:
|
||||
|
||||
```yaml
|
||||
catalog:
|
||||
rules:
|
||||
- allow: [Component, API, Template]
|
||||
```
|
||||
|
||||
For loading from a file, the following command should work when the backend is
|
||||
running:
|
||||
|
||||
```sh
|
||||
@@ -69,7 +92,7 @@ curl \
|
||||
--data-raw "{\"type\": \"file\", \"target\": \"${YOUR PATH HERE}/template.yaml\"}"
|
||||
```
|
||||
|
||||
If loading from a git location, you can run the following
|
||||
If loading from a Git location, you can run the following
|
||||
|
||||
```sh
|
||||
curl \
|
||||
@@ -83,7 +106,7 @@ This should then have added the catalog, and also should now be listed under the
|
||||
create page at http://localhost:3000/create.
|
||||
|
||||
Alternatively, if you want to get setup with some mock templates that are
|
||||
already provided for you, you can run the following to load those templates:
|
||||
already provided, run the following to load those templates:
|
||||
|
||||
```
|
||||
yarn lerna run mock-data
|
||||
|
||||
@@ -7,8 +7,8 @@ Publishers are responsible for pushing and storing the templated skeleton after
|
||||
the values have been templated by the `Templater`. See
|
||||
[Create your own templater](./create-your-own-templater.md) for more info.
|
||||
|
||||
They recieve a directory or location where the templater has sucessfully run on,
|
||||
and is now ready to store somewhere. They also get given some other options
|
||||
They receive a directory or location where the templater has sucessfully run
|
||||
and is now ready to store somewhere. They also are given some other options
|
||||
which are sent from the frontend, such as the `storePath` which is a string of
|
||||
where the frontend thinks we should save this templated folder.
|
||||
|
||||
@@ -17,7 +17,7 @@ Currently we provide the following `publishers`:
|
||||
- `github`
|
||||
|
||||
This publisher is passed through to the `createRouter` function of the
|
||||
`@spotify/plugin-scaffolder-backend`. Currently only one publisher is supported,
|
||||
`@spotify/plugin-scaffolder-backend`. Currently, only one publisher is supported,
|
||||
but PR's are always welcome.
|
||||
|
||||
An full example backend can be found
|
||||
|
||||
@@ -8,14 +8,14 @@ returned by the preparers, and then executing the templating command on top of
|
||||
the file and returning the completed template path. This may or may not be the
|
||||
same directory as the input directory.
|
||||
|
||||
They also recieve additional values from the frontend, which can be used to
|
||||
They also receive additional values from the frontend, which can be used to
|
||||
interpolate into the skeleton files.
|
||||
|
||||
Currently we provide the following templaters:
|
||||
|
||||
- `cookiecutter`
|
||||
|
||||
This templater is added the `TemplaterBuilder` and then passed into the
|
||||
This templater is added to the `TemplaterBuilder` and then passed into the
|
||||
`createRouter` function of the `@spotify/plugin-scaffolder-backend`
|
||||
|
||||
An full example backend can be found
|
||||
@@ -48,7 +48,7 @@ This `TemplaterKey` is used to select the correct templater from the
|
||||
`spec.templater` in the
|
||||
[Template Entity](../../software-catalog/descriptor-format.md#kind-template).
|
||||
|
||||
If you wish to add a new templater you'll need to register it with the
|
||||
If you wish to add a new templater, you'll need to register it with the
|
||||
`TemplaterBuilder`.
|
||||
|
||||
### Creating your own Templater to add to the `TemplaterBuilder`
|
||||
@@ -83,10 +83,10 @@ follows:
|
||||
- `dockerClient` - a [dockerode](https://github.com/apocas/dockerode) client to
|
||||
be able to run docker containers.
|
||||
|
||||
_note_ currently the templaters that we provide are basically docker action
|
||||
_note_ Currently the templaters that we provide are basically Docker action
|
||||
containers that are run on top of the skeleton folder. This keeps dependencies
|
||||
to a minimal for running backstage scaffolder, but you don't /have/ to use
|
||||
docker. You could create your own templater that spins up an EC2 instance and
|
||||
Docker. You could create your own templater that spins up an EC2 instance and
|
||||
downloads the folder and does everything using an AMI if you want. It's entirely
|
||||
up to you!
|
||||
|
||||
@@ -138,7 +138,7 @@ spec:
|
||||
description: Description of the component
|
||||
```
|
||||
|
||||
You see that the `spec.templater` is set as `handlebars`, you'll need to
|
||||
You see that the `spec.templater` is set as `handlebars`, so you'll need to
|
||||
register this with the `TemplaterBuilder` like so:
|
||||
|
||||
```ts
|
||||
|
||||
@@ -5,11 +5,11 @@ title: Extending the Scaffolder
|
||||
|
||||
Welcome. Take a seat. You're at the Scaffolder Documentation.
|
||||
|
||||
So - You wanna create stuff inside your company from some prebaked templates?
|
||||
So, you want to create stuff inside your company from some prebaked templates?
|
||||
You're at the right place.
|
||||
|
||||
This guide is gonna take you through how the Scaffolder in Backstage works.
|
||||
We'll dive into some jargon and run through whats going on in the backend to be
|
||||
This guide is going to take you through how the Scaffolder in Backstage works.
|
||||
We'll dive into some jargon and run through what's going on in the backend to be
|
||||
able to create these templates. There's also more guides that you might find
|
||||
useful at the bottom of this document. At it's core, theres 3 simple stages.
|
||||
|
||||
@@ -25,9 +25,9 @@ scaffolder that you will need to know:
|
||||
3. Publish
|
||||
|
||||
Each of these steps can be configured for your own use case, but we provide some
|
||||
sensible defaults too.
|
||||
sensible defaults, too.
|
||||
|
||||
Lets dive a little deeper into these phases.
|
||||
Let's dive a little deeper into these phases.
|
||||
|
||||
### Glossary and Jargon
|
||||
|
||||
@@ -38,8 +38,8 @@ the router to pick the correct `Preparer` to run for the `Template` entity.
|
||||
|
||||
**Templater** - The templater is responsible for actually running the chosen
|
||||
templater on top of the previously returned temporary directory from the
|
||||
**Preprarer**. We advise making these docker containers as it can keep all
|
||||
dependencies, for example Cookiecutter, self contained and not a dependency on
|
||||
**Preprarer**. We advise making these Docker containers as it can keep all
|
||||
dependencies--for example Cookiecutter--self contained and not a dependency on
|
||||
the host machine.
|
||||
|
||||
**Publisher** - The publisher is responsible for taking the finished directory,
|
||||
@@ -50,11 +50,11 @@ passed through to the scaffolder backend.
|
||||
|
||||
### How it works
|
||||
|
||||
The main of the heavy lifting is done in the
|
||||
Most of the heavy lifting is done in the
|
||||
[router.ts](https://github.com/spotify/backstage/blob/master/plugins/scaffolder-backend/src/service/router.ts#L93)
|
||||
file in the `scaffolder-backend` plugin.
|
||||
|
||||
There are 2 routes defined in the router. `POST /v1/jobs` and
|
||||
There are two routes defined in the router: `POST /v1/jobs` and
|
||||
`GET /v1/job/:jobId`
|
||||
|
||||
To create a scaffolding job, a JSON object containing the
|
||||
@@ -78,7 +78,7 @@ additional templating values must be posted as the post body.
|
||||
The values should represent something that is valid with the `schema` part of
|
||||
the [Template Entity](../../software-catalog/descriptor-format.md#kind-template)
|
||||
|
||||
Once that has been posted, a job will be setup with different stages. And the
|
||||
Once that has been posted, a job will be setup with different stages, and the
|
||||
job processor will complete each stage before moving onto the next stage, whilst
|
||||
collecting logs and mutating the running job.
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ title: Software Templates
|
||||
---
|
||||
|
||||
The Software Templates part of Backstage is a tool that can help you create
|
||||
Components inside Backstage. It by default has the ability to load skeletons of
|
||||
code, template in some variables and then publish the template to some location
|
||||
Components inside Backstage. By default, it has the ability to load skeletons of
|
||||
code, template in some variables, and then publish the template to some location
|
||||
like GitHub.
|
||||
|
||||
<video width="100%" height="100%" controls>
|
||||
@@ -33,8 +33,8 @@ internally.
|
||||

|
||||
|
||||
After filling in these variables, you'll get some more fields to fill out which
|
||||
are required for backstage usage. The owner, which is a `user` in the backstage
|
||||
system, and the `storePath` which right now must be a GitHub Organisation and a
|
||||
are required for backstage usage: the owner, (which is a `user` in the backstage
|
||||
system), the `storePath` (which right now must be a GitHub Organisation), and a
|
||||
non-existing github repository name in the format `organisation/reponame`.
|
||||
|
||||

|
||||
@@ -51,13 +51,13 @@ It shouldn't take too long, and you'll have a success screen!
|
||||

|
||||
|
||||
If it fails, you'll be able to click on each section to get the log from the
|
||||
step that failed which can be helpful to debug.
|
||||
step that failed which can be helpful in debugging.
|
||||
|
||||

|
||||
|
||||
### View Component in Catalog
|
||||
|
||||
When it's been created you'll see the `View in Catalog` button, which will take
|
||||
When it's been created, you'll see the `View in Catalog` button, which will take
|
||||
you to the registered component in the catalog:
|
||||
|
||||

|
||||
|
||||
@@ -3,9 +3,22 @@ id: index
|
||||
title: Running Backstage Locally
|
||||
---
|
||||
|
||||
First make sure you are using NodeJS with an Active LTS Release, currently v12.
|
||||
This is made easy with a version manager such as nvm which allows for version switching.
|
||||
|
||||
```bash
|
||||
# Checking your version
|
||||
node --version
|
||||
> v14.7.0
|
||||
|
||||
# Adding a second node version
|
||||
nvm install 12
|
||||
> Downloading and installing node v12.18.3...
|
||||
> Now using node v12.18.3 (npm v6.14.6)
|
||||
```
|
||||
|
||||
To get up and running with a local Backstage to evaluate it, let's clone it off
|
||||
of GitHub and run an initial build. First make sure that you have at least node
|
||||
version 12 installed locally.
|
||||
of GitHub and run an initial build.
|
||||
|
||||
```bash
|
||||
# Start from your local development folder
|
||||
|
||||
@@ -87,14 +87,17 @@ Example of tactics we have used to evangelize Backstage internally:
|
||||
- Pro-actively identify new plugins. Reach out to teams that own internal UIs or
|
||||
platforms that you think would make sense to consolidate into Backstage.
|
||||
|
||||
## Metrics
|
||||
## KPIs and metrics
|
||||
|
||||
These are some of the metrics that you can use to verify if Backstage has a
|
||||
successful impact on your software development process:
|
||||
|
||||
- **Onboarding time** Time until new engineers are productive. At Spotify we
|
||||
measure this as the time until the employee has merged their 10th PR (this
|
||||
metric was down 55% two years after deploying Backstage).
|
||||
metric was down 55% two years after deploying Backstage). Even though you may
|
||||
not be onboarding engineers at a rapid pace, this metric is a great proxy for
|
||||
the overall complexity of your ecosystem. Reducing it will therefore benefit
|
||||
your whole engineering organization, not just new joiners.
|
||||
|
||||
- **Number of merges per developer/day** Less time spent jumping between
|
||||
different tools and looking for information means more time to focus on
|
||||
@@ -142,3 +145,16 @@ Backstage as _the_ platform:
|
||||
|
||||
- % of contributions coming from outside the central Backstage team (currently
|
||||
85% inside Spotify)
|
||||
|
||||
- Traditional metrics such as visits (MAU, DAU, etc) and page views. Currently
|
||||
~50% of all Spotifiers use Backstage on a monthly basis, even though the
|
||||
percentage of engineers is below 50%. Most engineers actually use Backstage on
|
||||
a daily basis.
|
||||
|
||||
Again, any feedback is appreciated. Please use the Edit button at the top of the
|
||||
page to make a suggestion.
|
||||
|
||||
_**Note!** It might be tempting to try to optimize Backstage usage and
|
||||
"engagement". Even though you want to consolidate all your tooling and technical
|
||||
documentation in Backstage, it is important to remember that time spent in
|
||||
Backstage is time not spent writing code_ 🙃
|
||||
|
||||
@@ -79,18 +79,26 @@ guidelines to get started.
|
||||
|
||||
- Further improvements to platform documentation
|
||||
|
||||
### Plugins
|
||||
|
||||
Building and maintaining [plugins](https://backstage.io/plugins) is the work of
|
||||
the entire Backstage community.
|
||||
|
||||
A list of plugins that are in development is
|
||||
[available here](https://github.com/spotify/backstage/issues?q=is%3Aissue+is%3Aopen+label%3Aplugin+sort%3Areactions-%2B1-desc).
|
||||
We strongly recommend to upvote 👍 plugins you are interested in. This helps us
|
||||
and the community prioritize what plugins to build.
|
||||
|
||||
Are you missing a plugin for your favorite tool? Please
|
||||
[suggest a new one](https://github.com/spotify/backstage/issues/new?labels=plugin&template=plugin_template.md&title=%5BPlugin%5D+THE+PLUGIN+NAME).
|
||||
Chances are that someone will jump in and help build it.
|
||||
|
||||
### Future work 🔮
|
||||
|
||||
- **[Backstage platform is stable](https://github.com/spotify/backstage/milestone/19)** -
|
||||
The platform APIs and features are stable and can be depended on for
|
||||
production use. After this plugins will require little to no maintenance.
|
||||
|
||||
- **[Plugin marketplace](https://github.com/spotify/backstage/issues/2009)** -
|
||||
As the ecosystem of Backstage plugins continues to grow it is becoming
|
||||
increasingly hard to keep track of what plugins are available. To solve this
|
||||
we imagine a "Plugin marketplace" that helps with discovery and installation
|
||||
of plugins.
|
||||
|
||||
- **Deploy a product demo at `demo.backstage.io`** - Deploy a typical Backstage
|
||||
deployment available publicly so that people can click around and get a feel
|
||||
for the product without having to install anything.
|
||||
@@ -111,6 +119,7 @@ guidelines to get started.
|
||||
|
||||
### Completed milestones ✅
|
||||
|
||||
- [Plugin marketplace](https://backstage.io/plugins)
|
||||
- [Improved and move documentation to backstage.io](https://backstage.io/docs/overview/what-is-backstage)
|
||||
- [Backstage Service Catalog (alpha)](https://backstage.io/blog/2020/06/22/backstage-service-catalog-alpha)
|
||||
- [Backstage Software Templates (alpha)](https://backstage.io/blog/2020/08/05/announcing-backstage-software-templates)
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
id: add-to-marketplace
|
||||
title: Add to Marketplace
|
||||
---
|
||||
|
||||
## Adding a Plugin to the Marketplace
|
||||
|
||||
To add a new plugin to the [plugin marketplace](https://backstage.io/plugins)
|
||||
create a file in
|
||||
[`microsite/data/plugins`](https://github.com/spotify/backstage/tree/master/microsite/data/plugins)
|
||||
with your plugin's information. Example:
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: Your Plugin
|
||||
author: Your Name
|
||||
authorUrl: # A link to information about the author E.g. Company url, github user profile, etc
|
||||
category: Monitoring # A single category e.g. CI, Machine Learning, Services, Monitoring
|
||||
description: A brief description of the plugin. # Max 170 characters
|
||||
documentation: # A link to your documentation E.g. Your github README
|
||||
iconUrl: # Used as the src attribute for your logo.
|
||||
# You can provide an external url or add your logo under static/img and provide a path
|
||||
# relative to static/ e.g. img/my-logo.png
|
||||
npmPackageName: # Your npm package name E.g. '@backstage/plugin-<etc>' quotes are required
|
||||
```
|
||||
@@ -71,11 +71,7 @@ Example:
|
||||
```yaml
|
||||
# In app-config.yaml
|
||||
proxy:
|
||||
'/frobs':
|
||||
target: 'http://api.frobsco.com/v1'
|
||||
changeOrigin: true
|
||||
pathRewrite:
|
||||
'^/proxy/frobs/': '/'
|
||||
'/frobs': http://api.frobsco.com/v1
|
||||
```
|
||||
|
||||
```ts
|
||||
@@ -86,9 +82,8 @@ fetch(`${backendUrl}/proxy/frobs/list`)
|
||||
.then(payload => setFrobs(payload as Frob[]));
|
||||
```
|
||||
|
||||
The proxy is powered by the `http-proxy-middleware` package, and supports all of
|
||||
its
|
||||
[configuration options](https://github.com/chimurai/http-proxy-middleware#options).
|
||||
The proxy is powered by the `http-proxy-middleware` package. See
|
||||
[Proxying](proxying.md) for a full description of its configuration options.
|
||||
|
||||
Internally at Spotify, the proxy option has been the overwhelmingly most popular
|
||||
choice for plugin makers. Since we have DNS based service discovery in place and
|
||||
|
||||
@@ -21,8 +21,15 @@ To create a plugin, follow the steps outlined [here](create-a-plugin.md).
|
||||
|
||||
If you start developing a plugin that you aim to release as open source, we
|
||||
suggest that you create a new
|
||||
[new Issue](https://github.com/spotify/backstage/issues/new?template=plugin_template.md).
|
||||
[new Issue](https://github.com/spotify/backstage/issues/new?labels=plugin&template=plugin_template.md&title=%5BPlugin%5D+THE+PLUGIN+NAME).
|
||||
This helps the community know what plugins are in development.
|
||||
|
||||
You can also use this process if you have an idea for a good plugin but you hope
|
||||
that someone else will pick up the work.
|
||||
|
||||
## Integrate into the Service Catalog
|
||||
|
||||
If your plugin isn't supposed to live as a standalone page, but rather needs to
|
||||
be presented as a part of a Service Catalog (e.g. a separate tab or a card on an
|
||||
"Overview" tab), then check out
|
||||
[the instruction](integrating-plugin-into-service-catalog.md). on how to do it.
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
---
|
||||
id: integrating-plugin-into-service-catalog
|
||||
title: Integrate into the Service Catalog
|
||||
---
|
||||
|
||||
> This is an advanced use case and currently is an experimental feature. Expect
|
||||
> API to change over time
|
||||
|
||||
## Steps
|
||||
|
||||
1. [Create a plugin](#create-a-plugin)
|
||||
1. [Export a router with relative routes](#export-a-router)
|
||||
1. [Import and use router in the APP](#import-and-use-router-in-the-app)
|
||||
|
||||
### Create a plugin
|
||||
|
||||
Follow the [same process](create-a-plugin.md) as for standalone plugin. You
|
||||
should have a separate package in a folder, which represents your plugin.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
$ yarn create-plugin
|
||||
> ? Enter an ID for the plugin [required] my-plugin
|
||||
> ? Enter the owner(s) of the plugin. If specified, this will be added to CODEOWNERS for the plugin path. [optional]
|
||||
|
||||
Creating the plugin...
|
||||
```
|
||||
|
||||
### Export a router
|
||||
|
||||
Now in the plugin you have a `Router.tsx` file in the `src` folder. By default
|
||||
it contains only one example route. Create a routing structure needed for your
|
||||
plugin, keeping in mind that the whole set of routes defined here are going to
|
||||
be mounted under some different route in the App.
|
||||
|
||||
Example:
|
||||
|
||||
`my-plugin` consists of 2 different views - `/me` and `/about`. I envision
|
||||
people integrating it into plugin catalog as a tab named "MyPlugin". Then, my
|
||||
`Routes.tsx` for the plugin is going to look like:
|
||||
|
||||
```tsx
|
||||
<Routes>
|
||||
<Route path="/me" element={<MePage />} />
|
||||
<Route path="/about" element={<AboutPage />} />
|
||||
</Routes>
|
||||
```
|
||||
|
||||
(where MePage and AboutPage are 2 components defined in your plugin and imported
|
||||
accordingly inside `Router.tsx`)
|
||||
|
||||
> Pay attention, if your `MePage` references the `AboutPage` it needs to do it
|
||||
> through link to `about`, not `/about`. This allows react-router v6 to enable
|
||||
> its relative routing mechanism. Read more -
|
||||
> https://reacttraining.com/blog/react-router-v6-pre/#relative-route-path-and-link-to
|
||||
|
||||
### Import and use router in the APP
|
||||
|
||||
In the `app/src/components/catalog/EntityPage.tsx` (app === your folder,
|
||||
containing backstage app) import your created Router:
|
||||
|
||||
```tsx
|
||||
import { Router as MyPluginRouter } from '@backstage/plugin-my-plugin;
|
||||
```
|
||||
|
||||
Now, you need to mount `MyPluginRouter` onto some route, for example if you had:
|
||||
|
||||
```tsx
|
||||
const DefaultEntityPage = ({ entity }: { entity: Entity }) => (
|
||||
<EntityPageLayout>
|
||||
<EntityPageLayout.Content
|
||||
path="/"
|
||||
title="Overview"
|
||||
element={<OverviewPage entity={entity} />}
|
||||
/>
|
||||
</EntityPageLayout>
|
||||
);
|
||||
```
|
||||
|
||||
after you add your code it becomes:
|
||||
|
||||
```tsx
|
||||
const DefaultEntityPage = ({ entity }: { entity: Entity }) => (
|
||||
<EntityPageLayout>
|
||||
<EntityPageLayout.Content
|
||||
path="/"
|
||||
title="Overview"
|
||||
element={<OverviewPage entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/my-plugin"
|
||||
title="My Plugin"
|
||||
element={<MyPluginRouter entity={entity} />}
|
||||
/>
|
||||
</EntityPageLayout>
|
||||
);
|
||||
```
|
||||
|
||||
All of magic happens thanks to the `EntityPageLayout` component, which comes as
|
||||
an export from `@backstage/plugin-catalog` package.
|
||||
|
||||
```tsx
|
||||
type EntityPageLayoutContentProps = {
|
||||
/**
|
||||
* Going to be transformed into react-router v6
|
||||
* path under the hood. Read more at https://reacttraining.com/blog/react-router-v6-pre
|
||||
*/
|
||||
path: string;
|
||||
/**
|
||||
* Gets transformed into the title for the tab
|
||||
*/
|
||||
title: string;
|
||||
/**
|
||||
* Element that is rendered when the location
|
||||
* matches the path provided
|
||||
*/
|
||||
element: JSX.Element;
|
||||
};
|
||||
```
|
||||
|
||||
> You can either pass the entity from App to the plugin's router as a prop or
|
||||
> use `useEntity` hook from `@backstage/plugin-catalog` directly inside your
|
||||
> plugin.
|
||||
@@ -3,4 +3,73 @@ id: proxying
|
||||
title: Proxying
|
||||
---
|
||||
|
||||
## TODO
|
||||
## Overview
|
||||
|
||||
The Backstage backend comes packaged with a basic HTTP proxy, that can aid in
|
||||
reaching backend service APIs from frontend plugin code. See
|
||||
[Call Existing API](call-existing-api.md) for a description of when the proxy
|
||||
can be the best choice for communicating with an API.
|
||||
|
||||
## Getting Started
|
||||
|
||||
The plugin is already added to a default Backstage project.
|
||||
|
||||
In `packages/backend/src/index.ts`:
|
||||
|
||||
```ts
|
||||
const proxyEnv = useHotMemoize(module, () => createEnv('proxy'));
|
||||
|
||||
const service = createServiceBuilder(module)
|
||||
.loadConfig(configReader)
|
||||
/** ... other routers ... */
|
||||
.addRouter('/proxy', await proxy(proxyEnv, '/proxy'));
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Configuration for the proxy plugin lives under a `proxy` root key of your
|
||||
`app-config.yaml` file.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
# in app-config.yaml
|
||||
proxy:
|
||||
'/simple-example': http://simple.example.com:8080
|
||||
'/larger-example/v1':
|
||||
target: http://larger.example.com:8080/svc.v1
|
||||
headers:
|
||||
Authorization:
|
||||
$secret:
|
||||
env: EXAMPLE_AUTH_HEADER
|
||||
```
|
||||
|
||||
Each key under the proxy configuration entry is a route to match, below the
|
||||
prefix that the proxy plugin is mounted on. It must start with a slash. For
|
||||
example, if the backend mounts the proxy plugin as `/proxy`, the above
|
||||
configuration will lead to the proxy acting on backend requests to
|
||||
`/proxy/simple-example/...` and `/proxy/larger-example/v1/...`.
|
||||
|
||||
The value inside each route is either a simple URL string, or an object on the
|
||||
format accepted by
|
||||
[http-proxy-middleware](https://www.npmjs.com/package/http-proxy-middleware).
|
||||
|
||||
If the value is a string, it is assumed to correspond to:
|
||||
|
||||
```yaml
|
||||
target: <the string>
|
||||
changeOrigin: true
|
||||
pathRewrite:
|
||||
'^<url prefix><the string>/': '/'
|
||||
```
|
||||
|
||||
When the target is an object, it is given verbatim to `http-proxy-middleware`
|
||||
except with the following caveats for convenience:
|
||||
|
||||
- If `changeOrigin` is not specified, it is set to `true`. This is the most
|
||||
commonly useful value.
|
||||
- If `pathRewrite` is not specified, it is set to a single rewrite that removes
|
||||
the entire prefix and route. In the above example, a rewrite of
|
||||
`'^/proxy/larger-example/v1/': '/'` is added. That means that a request to
|
||||
`/proxy/larger-example/v1/some/path` will be translated to a request to
|
||||
`http://larger.example.com:8080/svc.v1/some/path`.
|
||||
|
||||
@@ -39,4 +39,18 @@ $ git push origin -u new-release
|
||||
And then create a PR. Once the PR is approved and merged into master, the master
|
||||
build will publish new versions of all bumped packages.
|
||||
|
||||
### Include new changes in existing release PR
|
||||
|
||||
If you want to include some last minute changes to an existing release PR,
|
||||
follow these instructions:
|
||||
|
||||
```sh
|
||||
$ git checkout master
|
||||
$ git pull
|
||||
$ git checkout new-release
|
||||
$ git reset --hard master
|
||||
$ yarn release
|
||||
$ git push --force
|
||||
```
|
||||
|
||||
[Back to Docs](../README.md)
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
| app.resources | Kubernetes Pod resource requests/limits | `{}` |
|
||||
| app.nodeSelector | Node selectors for scheduling app/frontend pods | `{}` |
|
||||
| app.tolerations | Tolerations for scheduling app/frontend pods | `{}` |
|
||||
| app.affinity | Affinity setttings for scheduling app/frontend pods | `{}` |
|
||||
| app.affinity | Affinity settings for scheduling app/frontend pods | `{}` |
|
||||
|
||||
## Backend Values
|
||||
|
||||
@@ -48,4 +48,4 @@
|
||||
| backend.resources | Kubernetes Pod resource requests/limits | `{}` |
|
||||
| backend.nodeSelector | Node selectors for scheduling backend pods | `{}` |
|
||||
| backend.tolerations | Tolerations for scheduling backend pods | `{}` |
|
||||
| backend.affinity | Affinity setttings for scheduling backend pods | `{}` |
|
||||
| backend.affinity | Affinity settings for scheduling backend pods | `{}` |
|
||||
|
||||
@@ -45,6 +45,9 @@ class Footer extends React.Component {
|
||||
<div>
|
||||
<h5>Community</h5>
|
||||
<a href="https://discord.gg/MUpMjP2">Support chatroom</a>
|
||||
<a href="https://github.com/spotify/backstage/blob/master/CONTRIBUTING.md">
|
||||
Contributing
|
||||
</a>
|
||||
<a href="https://mailchi.mp/spotify/backstage-community">
|
||||
Subscribe to our newsletter
|
||||
</a>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: API Docs
|
||||
author: SDA SE
|
||||
authorUrl: https://sda.se/
|
||||
category: Discovery
|
||||
description: Components to discover and display API entities as an extension to the catalog plugin.
|
||||
documentation: https://github.com/spotify/backstage/blob/master/plugins/api-docs/README.md
|
||||
iconUrl: https://thecoders.io/wp-content/uploads/2019/11/tech-swagger.svg
|
||||
npmPackageName: '@backstage/plugin-api-docs'
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
title: CircleCI
|
||||
author: Spotify
|
||||
authorUrl: https://github.com/spotify
|
||||
category: CI
|
||||
description: Automate your development process with CI hosted in the cloud or on a private server.
|
||||
documentation: https://github.com/spotify/backstage/tree/master/plugins/circleci
|
||||
iconUrl: https://www.saaves.com/storage/brochure/logo-circleci-icon1583764538.png
|
||||
npmPackageName: '@backstage/plugin-circleci'
|
||||
tags:
|
||||
- ci
|
||||
- cd
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
title: GitHub Actions
|
||||
author: Spotify
|
||||
authorUrl: https://github.com/spotify
|
||||
category: CI
|
||||
description: GitHub Actions makes it easy to automate all your software workflows, now with world-class CI/CD. Build, test, and deploy your code right from GitHub.
|
||||
documentation: https://github.com/spotify/backstage/tree/master/plugins/github-actions
|
||||
iconUrl: https://avatars2.githubusercontent.com/u/44036562?s=400&v=4
|
||||
npmPackageName: '@backstage/plugin-github-actions'
|
||||
tags:
|
||||
- ci
|
||||
- cd
|
||||
- github
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
title: GitHub Pull Requests
|
||||
author: roadie.io
|
||||
authorUrl: https://roadie.io/
|
||||
category: CI
|
||||
description: View GitHub pull requests for your service in Backstage.
|
||||
documentation: https://roadie.io/backstage/plugins/github-pull-requests
|
||||
iconUrl: https://roadie.io/static/7f13bb8d861d8dedc5112fb939d215f9/351f2/GitHub-Mark-Light-120px-plus.png
|
||||
npmPackageName: '@roadiehq/backstage-plugin-github-pull-requests'
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
title: GitOps Clusters
|
||||
author: Weaveworks
|
||||
authorUrl: https://www.weave.works/
|
||||
category: Kubernetes
|
||||
description: Create GitOps-managed Kubernetes clusters. Currently, it supports provisioning EKS clusters on GitHub via GitHub Actions.
|
||||
documentation: https://github.com/spotify/backstage/tree/master/plugins/gitops-profiles
|
||||
iconUrl: https://res-5.cloudinary.com/crunchbase-production/image/upload/c_lpad,h_256,w_256,f_auto,q_auto:eco/v1462316670/i9d3delzvx1erzjhmcws.png
|
||||
npmPackageName: '@backstage/plugin-gitops-profiles'
|
||||
tags:
|
||||
- kubernetes
|
||||
- gitops
|
||||
- github
|
||||
- eks
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
title: GraphiQL
|
||||
author: Spotify
|
||||
authorUrl: https://github.com/spotify
|
||||
category: Debugging
|
||||
description: Integrates GraphiQL as a tool to browse GraphiQL endpoints inside Backstage.
|
||||
documentation: https://github.com/spotify/backstage/tree/master/plugins/lighthouse
|
||||
iconUrl: https://upload.wikimedia.org/wikipedia/commons/thumb/1/17/GraphQL_Logo.svg/1024px-GraphQL_Logo.svg.png
|
||||
npmPackageName: '@backstage/plugin-graphiql'
|
||||
tags:
|
||||
- graphql
|
||||
- github
|
||||
- gitlab
|
||||
- api
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
title: Jenkins
|
||||
author: '@timja'
|
||||
authorUrl: https://github.com/timja
|
||||
category: CI
|
||||
description: Jenkins offers a simple way to set up a continuous integration and continuous delivery environment.
|
||||
documentation: https://github.com/spotify/backstage/tree/master/plugins/jenkins
|
||||
iconUrl: https://img.icons8.com/color/1600/jenkins.png
|
||||
npmPackageName: '@backstage/plugin-jenkins'
|
||||
tags:
|
||||
- ci
|
||||
- cd
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
title: Lighthouse
|
||||
author: Spotify
|
||||
authorUrl: https://github.com/spotify
|
||||
category: Accessibility
|
||||
description: Google's Lighthouse tool is a great resource for benchmarking and improving the accessibility, performance, SEO, and best practices of your website.
|
||||
documentation: https://github.com/spotify/backstage/tree/master/plugins/lighthouse
|
||||
iconUrl: https://seeklogo.com/images/G/google-lighthouse-logo-1C7FA08580-seeklogo.com.png
|
||||
npmPackageName: '@backstage/plugin-lighthouse'
|
||||
tags:
|
||||
- web
|
||||
- seo
|
||||
- accessibility
|
||||
- performance
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
title: New Relic
|
||||
author: '@timwheelercom'
|
||||
authorUrl: https://github.com/timwheelercom
|
||||
category: Monitoring
|
||||
description: Observability platform built to help engineers create and monitor their software.
|
||||
documentation: https://github.com/spotify/backstage/tree/master/plugins/newrelic
|
||||
iconUrl: https://www.mulesoft.com/sites/default/files/2018-10/New_relic.png
|
||||
npmPackageName: '@backstage/plugin-newrelic'
|
||||
tags:
|
||||
- performance
|
||||
- monitoring
|
||||
- errors
|
||||
- alerting
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
title: Rollbar
|
||||
author: '@andrewthauer'
|
||||
authorUrl: https://github.com/andrewthauer
|
||||
category: Monitoring
|
||||
description: View Rollbar errors for your services in Backstage.
|
||||
documentation: https://github.com/spotify/backstage/tree/master/plugins/rollbar
|
||||
iconUrl: https://rollbar.com/assets/media/rollbar-mark-color.png
|
||||
npmPackageName: '@backstage/plugin-rollbar'
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: Sentry
|
||||
author: Spotify
|
||||
authorUrl: https://github.com/spotify
|
||||
category: Monitoring
|
||||
description: View Sentry issues in Backstage.
|
||||
documentation: https://github.com/spotify/backstage/tree/master/plugins/sentry
|
||||
iconUrl: https://sentry-brand.storage.googleapis.com/sentry-glyph-white.png
|
||||
npmPackageName: '@backstage/plugin-sentry'
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: Tech Radar
|
||||
author: Spotify
|
||||
authorUrl: https://github.com/spotify
|
||||
category: Discovery
|
||||
description: Visualize the your company's official guidelines of different areas of software development.
|
||||
documentation: https://github.com/spotify/backstage/tree/master/plugins/tech-radar
|
||||
iconUrl: https://www.materialui.co/materialIcons/action/track_changes_white_192x192.png
|
||||
npmPackageName: '@backstage/plugin-tech-radar'
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
title: Travis CI
|
||||
author: roadie.io
|
||||
authorUrl: https://roadie.io/
|
||||
category: CI
|
||||
description: View Travis CI builds for your service in Backstage.
|
||||
documentation: https://roadie.io/backstage/plugins/travis-ci
|
||||
iconUrl: https://roadie.io/static/af2941eaf0af675facb281d566f42e14/45f2b/travis-ci-mascot-200x200.png
|
||||
npmPackageName: '@roadiehq/backstage-plugin-travis-ci'
|
||||
|
||||
@@ -1,321 +0,0 @@
|
||||
{
|
||||
"_comment": "This file is auto-generated by write-translations.js",
|
||||
"localized-strings": {
|
||||
"next": "Next",
|
||||
"previous": "Previous",
|
||||
"tagline": "An open platform for building developer portals",
|
||||
"docs": {
|
||||
"api/backend": {
|
||||
"title": "Backend"
|
||||
},
|
||||
"api/utility-apis": {
|
||||
"title": "Utility APIs"
|
||||
},
|
||||
"architecture-decisions/adrs-adr001": {
|
||||
"title": "ADR001: Architecture Decision Record (ADR) log",
|
||||
"sidebar_label": "ADR001"
|
||||
},
|
||||
"architecture-decisions/adrs-adr002": {
|
||||
"title": "ADR002: Default Software Catalog File Format",
|
||||
"sidebar_label": "ADR002"
|
||||
},
|
||||
"architecture-decisions/adrs-adr003": {
|
||||
"title": "ADR003: Avoid Default Exports and Prefer Named Exports",
|
||||
"sidebar_label": "ADR003"
|
||||
},
|
||||
"architecture-decisions/adrs-adr004": {
|
||||
"title": "ADR004: Module Export Structure",
|
||||
"sidebar_label": "ADR004"
|
||||
},
|
||||
"architecture-decisions/adrs-adr005": {
|
||||
"title": "ADR005: Catalog Core Entities",
|
||||
"sidebar_label": "ADR005"
|
||||
},
|
||||
"architecture-decisions/adrs-adr006": {
|
||||
"title": "ADR006: Avoid React.FC and React.SFC",
|
||||
"sidebar_label": "ADR006"
|
||||
},
|
||||
"architecture-decisions/adrs-adr007": {
|
||||
"title": "ADR007: Use MSW to mock http requests",
|
||||
"sidebar_label": "ADR007"
|
||||
},
|
||||
"architecture-decisions/adrs-adr008": {
|
||||
"title": "ADR008: Default Catalog File Name",
|
||||
"sidebar_label": "ADR008"
|
||||
},
|
||||
"architecture-decisions/adrs-overview": {
|
||||
"title": "Architecture Decision Records (ADR)",
|
||||
"sidebar_label": "Overview"
|
||||
},
|
||||
"auth/add-auth-provider": {
|
||||
"title": "Adding authentication providers"
|
||||
},
|
||||
"auth/auth-backend-classes": {
|
||||
"title": "Auth backend classes"
|
||||
},
|
||||
"auth/auth-backend": {
|
||||
"title": "Auth backend"
|
||||
},
|
||||
"auth/glossary": {
|
||||
"title": "Glossary"
|
||||
},
|
||||
"auth/index": {
|
||||
"title": "User Authentication and Authorization in Backstage"
|
||||
},
|
||||
"auth/oauth": {
|
||||
"title": "OAuth and OpenID Connect"
|
||||
},
|
||||
"conf/defining": {
|
||||
"title": "Defining Configuration for your Plugin"
|
||||
},
|
||||
"conf/index": {
|
||||
"title": "Static Configuration in Backstage"
|
||||
},
|
||||
"conf/reading": {
|
||||
"title": "Reading Backstage Configuration"
|
||||
},
|
||||
"conf/writing": {
|
||||
"title": "Writing Backstage Configuration Files"
|
||||
},
|
||||
"dls/contributing-to-storybook": {
|
||||
"title": "Contributing to Storybook"
|
||||
},
|
||||
"dls/design": {
|
||||
"title": "Design"
|
||||
},
|
||||
"dls/figma": {
|
||||
"title": "Figma"
|
||||
},
|
||||
"FAQ": {
|
||||
"title": "FAQ"
|
||||
},
|
||||
"features/software-catalog/software-catalog-api": {
|
||||
"title": "API"
|
||||
},
|
||||
"features/software-catalog/descriptor-format": {
|
||||
"title": "Descriptor Format of Catalog Entities",
|
||||
"sidebar_label": "YAML File Format"
|
||||
},
|
||||
"features/software-catalog/extending-the-model": {
|
||||
"title": "Extending the model"
|
||||
},
|
||||
"features/software-catalog/external-integrations": {
|
||||
"title": "External integrations"
|
||||
},
|
||||
"features/software-catalog/software-catalog-overview": {
|
||||
"title": "Backstage Service Catalog (alpha)",
|
||||
"sidebar_label": "Backstage Service Catalog"
|
||||
},
|
||||
"features/software-catalog/installation": {
|
||||
"title": "features/software-catalog/installation"
|
||||
},
|
||||
"features/software-catalog/system-model": {
|
||||
"title": "System Model"
|
||||
},
|
||||
"features/software-templates/adding-templates": {
|
||||
"title": "Adding your own Templates"
|
||||
},
|
||||
"features/software-templates/extending/extending-preparer": {
|
||||
"title": "Create your own Preparer"
|
||||
},
|
||||
"features/software-templates/extending/extending-publisher": {
|
||||
"title": "Create your own Publisher"
|
||||
},
|
||||
"features/software-templates/extending/extending-templater": {
|
||||
"title": "Creating your own Templater"
|
||||
},
|
||||
"features/software-templates/extending/extending-index": {
|
||||
"title": "Extending the Scaffolder"
|
||||
},
|
||||
"features/software-templates/software-templates-index": {
|
||||
"title": "Software Templates"
|
||||
},
|
||||
"features/software-templates/installation": {
|
||||
"title": "features/software-templates/installation"
|
||||
},
|
||||
"features/techdocs/concepts": {
|
||||
"title": "Concepts"
|
||||
},
|
||||
"features/techdocs/creating-and-publishing": {
|
||||
"title": "Creating and publishing your docs",
|
||||
"sidebar_label": "Creating and Publishing Documentation"
|
||||
},
|
||||
"features/techdocs/faqs": {
|
||||
"title": "TechDocs FAQ",
|
||||
"sidebar_label": "FAQ"
|
||||
},
|
||||
"features/techdocs/getting-started": {
|
||||
"title": "Getting Started"
|
||||
},
|
||||
"features/techdocs/techdocs-overview": {
|
||||
"title": "TechDocs Documentation",
|
||||
"sidebar_label": "Overview"
|
||||
},
|
||||
"getting-started/app-custom-theme": {
|
||||
"title": "Customize the look-and-feel of your App"
|
||||
},
|
||||
"getting-started/configure-app-with-plugins": {
|
||||
"title": "Configuring App with plugins"
|
||||
},
|
||||
"getting-started/create-an-app": {
|
||||
"title": "Create an App"
|
||||
},
|
||||
"getting-started/deployment-k8s": {
|
||||
"title": "Kubernetes"
|
||||
},
|
||||
"getting-started/deployment-other": {
|
||||
"title": "Other"
|
||||
},
|
||||
"getting-started/development-environment": {
|
||||
"title": "Development Environment"
|
||||
},
|
||||
"getting-started/index": {
|
||||
"title": "Running Backstage Locally"
|
||||
},
|
||||
"getting-started/installation": {
|
||||
"title": "Installation"
|
||||
},
|
||||
"overview/adopting": {
|
||||
"title": "Strategies for adopting"
|
||||
},
|
||||
"overview/architecture-overview": {
|
||||
"title": "Architecture overview"
|
||||
},
|
||||
"overview/architecture-terminology": {
|
||||
"title": "Architecture terminology"
|
||||
},
|
||||
"overview/background": {
|
||||
"title": "The Spotify Story"
|
||||
},
|
||||
"overview/roadmap": {
|
||||
"title": "Project roadmap"
|
||||
},
|
||||
"overview/support": {
|
||||
"title": "Support and community"
|
||||
},
|
||||
"overview/vision": {
|
||||
"title": "Vision"
|
||||
},
|
||||
"overview/what-is-backstage": {
|
||||
"title": "What is Backstage?"
|
||||
},
|
||||
"plugins/backend-plugin": {
|
||||
"title": "Backend plugin"
|
||||
},
|
||||
"plugins/call-existing-api": {
|
||||
"title": "Call Existing API"
|
||||
},
|
||||
"plugins/create-a-plugin": {
|
||||
"title": "Create a Backstage Plugin"
|
||||
},
|
||||
"plugins/existing-plugins": {
|
||||
"title": "Existing plugins"
|
||||
},
|
||||
"plugins/index": {
|
||||
"title": "Intro to plugins"
|
||||
},
|
||||
"plugins/plugin-development": {
|
||||
"title": "Plugin Development"
|
||||
},
|
||||
"plugins/proxying": {
|
||||
"title": "Proxying"
|
||||
},
|
||||
"plugins/publish-private": {
|
||||
"title": "Publish private"
|
||||
},
|
||||
"plugins/publishing": {
|
||||
"title": "Publishing"
|
||||
},
|
||||
"plugins/structure-of-a-plugin": {
|
||||
"title": "Structure of a Plugin"
|
||||
},
|
||||
"plugins/testing": {
|
||||
"title": "Testing with Jest"
|
||||
},
|
||||
"README": {
|
||||
"title": "README"
|
||||
},
|
||||
"reference/createPlugin-feature-flags": {
|
||||
"title": "createPlugin - feature flags"
|
||||
},
|
||||
"reference/createPlugin-router": {
|
||||
"title": "createPlugin - router"
|
||||
},
|
||||
"reference/createPlugin": {
|
||||
"title": "createPlugin"
|
||||
},
|
||||
"reference/utility-apis/AlertApi": {
|
||||
"title": "reference/utility-apis/AlertApi"
|
||||
},
|
||||
"reference/utility-apis/AppThemeApi": {
|
||||
"title": "reference/utility-apis/AppThemeApi"
|
||||
},
|
||||
"reference/utility-apis/BackstageIdentityApi": {
|
||||
"title": "reference/utility-apis/BackstageIdentityApi"
|
||||
},
|
||||
"reference/utility-apis/Config": {
|
||||
"title": "reference/utility-apis/Config"
|
||||
},
|
||||
"reference/utility-apis/ErrorApi": {
|
||||
"title": "reference/utility-apis/ErrorApi"
|
||||
},
|
||||
"reference/utility-apis/FeatureFlagsApi": {
|
||||
"title": "reference/utility-apis/FeatureFlagsApi"
|
||||
},
|
||||
"reference/utility-apis/IdentityApi": {
|
||||
"title": "reference/utility-apis/IdentityApi"
|
||||
},
|
||||
"reference/utility-apis/OAuthApi": {
|
||||
"title": "reference/utility-apis/OAuthApi"
|
||||
},
|
||||
"reference/utility-apis/OAuthRequestApi": {
|
||||
"title": "reference/utility-apis/OAuthRequestApi"
|
||||
},
|
||||
"reference/utility-apis/OpenIdConnectApi": {
|
||||
"title": "reference/utility-apis/OpenIdConnectApi"
|
||||
},
|
||||
"reference/utility-apis/ProfileInfoApi": {
|
||||
"title": "reference/utility-apis/ProfileInfoApi"
|
||||
},
|
||||
"reference/utility-apis/README": {
|
||||
"title": "Utility API References"
|
||||
},
|
||||
"reference/utility-apis/SessionStateApi": {
|
||||
"title": "reference/utility-apis/SessionStateApi"
|
||||
},
|
||||
"reference/utility-apis/StorageApi": {
|
||||
"title": "reference/utility-apis/StorageApi"
|
||||
},
|
||||
"tutorials/journey": {
|
||||
"title": "Future developer journey"
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"GitHub": "GitHub",
|
||||
"Docs": "Docs",
|
||||
"Blog": "Blog",
|
||||
"Demos": "Demos",
|
||||
"Newsletter": "Newsletter"
|
||||
},
|
||||
"categories": {
|
||||
"Overview": "Overview",
|
||||
"Getting Started": "Getting Started",
|
||||
"Features": "Features",
|
||||
"Plugins": "Plugins",
|
||||
"Configuration": "Configuration",
|
||||
"Auth and identity": "Auth and identity",
|
||||
"Designing for Backstage": "Designing for Backstage",
|
||||
"API references": "API references",
|
||||
"Tutorials": "Tutorials",
|
||||
"Architecture Decision Records (ADRs)": "Architecture Decision Records (ADRs)",
|
||||
"Contribute": "Contribute",
|
||||
"Support": "Support",
|
||||
"FAQ": "FAQ"
|
||||
}
|
||||
},
|
||||
"pages-strings": {
|
||||
"Help Translate|recruit community translators for your project": "Help Translate",
|
||||
"Edit this Doc|recruitment message asking to edit the doc source": "Edit",
|
||||
"Translate this Doc|recruitment message asking to translate the docs": "Translate"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "0.1.1-alpha.20",
|
||||
"version": "0.1.1-alpha.21",
|
||||
"name": "backstage-microsite",
|
||||
"license": "Apache-2.0",
|
||||
"private": true,
|
||||
@@ -13,6 +13,7 @@
|
||||
"rename-version": "docusaurus-rename-version"
|
||||
},
|
||||
"devDependencies": {
|
||||
"docusaurus": "^2.0.0-alpha.61"
|
||||
"docusaurus": "^2.0.0-alpha.61",
|
||||
"js-yaml": "^3.14.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -455,12 +455,8 @@ class Index extends React.Component {
|
||||
Share with the community
|
||||
</Block.SmallTitle>
|
||||
<Block.Paragraph>
|
||||
Building{' '}
|
||||
<a href="https://github.com/spotify/backstage/blob/master/docs/FAQ.md#how-do-i-find-out-if-a-plugin-already-exists">
|
||||
open source plugins
|
||||
</a>{' '}
|
||||
contributes to the entire Backstage ecosystem, which benefits
|
||||
everyone
|
||||
Building <a href="/plugins">open source plugins</a> contributes
|
||||
to the entire Backstage ecosystem, which benefits everyone
|
||||
</Block.Paragraph>
|
||||
</Block.TextBox>
|
||||
|
||||
@@ -472,7 +468,7 @@ class Index extends React.Component {
|
||||
|
||||
<ActionBlock className="stripe-top bg-teal">
|
||||
<ActionBlock.Title>Build a plugin</ActionBlock.Title>
|
||||
<ActionBlock.Link href="https://github.com/spotify/backstage/blob/master/docs/plugins/create-a-plugin.md">
|
||||
<ActionBlock.Link href="/docs/plugins/create-a-plugin">
|
||||
Contribute
|
||||
</ActionBlock.Link>
|
||||
</ActionBlock>
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* Copyright (c) 2017-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const yaml = require('js-yaml');
|
||||
const React = require('react');
|
||||
const Components = require(`${process.cwd()}/core/Components.js`);
|
||||
const {
|
||||
Block: { Container },
|
||||
BulletLine,
|
||||
} = Components;
|
||||
|
||||
const pluginsDirectory = require('path').join(process.cwd(), 'data/plugins');
|
||||
const pluginMetadata = fs
|
||||
.readdirSync(pluginsDirectory)
|
||||
.map(file =>
|
||||
yaml.safeLoad(fs.readFileSync(`./data/plugins/${file}`, 'utf8')),
|
||||
);
|
||||
const truncate = text =>
|
||||
text.length > 170 ? text.substr(0, 170) + '...' : text;
|
||||
|
||||
const addPluginDocsLink = '/docs/plugins/add-to-marketplace';
|
||||
const defaultIconUrl = 'img/logo-gradient-on-dark.svg';
|
||||
|
||||
const Plugins = () => (
|
||||
<main className="MainContent">
|
||||
<div className="PluginPageLayout">
|
||||
<div className="PluginPageHeader">
|
||||
<h2>Plugin marketplace</h2>
|
||||
<p>
|
||||
Open source plugins that you can add to your Backstage deployment.
|
||||
Learn how to build a <a href="/docs/plugins">plugin</a>.
|
||||
</p>
|
||||
<span>
|
||||
<a
|
||||
className="PluginAddNewButton ButtonFilled"
|
||||
href={addPluginDocsLink}
|
||||
>
|
||||
<b>Add to marketplace</b>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<BulletLine style={{ width: '100% ' }} />
|
||||
<Container wrapped className="grid">
|
||||
{pluginMetadata.map(
|
||||
({
|
||||
iconUrl,
|
||||
title,
|
||||
description,
|
||||
author,
|
||||
authorUrl,
|
||||
documentation,
|
||||
category,
|
||||
}) => (
|
||||
<div className="PluginCard">
|
||||
<div className="PluginCardHeader">
|
||||
<img src={iconUrl || defaultIconUrl} alt={title} />
|
||||
<h2 className="PluginCardTitle">{title}</h2>
|
||||
<p className="Author">
|
||||
by <a href={authorUrl}>{author}</a>
|
||||
</p>
|
||||
<span className="ChipOutlined">{category}</span>
|
||||
</div>
|
||||
<div className="PluginCardBody">
|
||||
<p>{truncate(description)}</p>
|
||||
</div>
|
||||
<Container className="PluginCardFooter">
|
||||
<span>
|
||||
<a
|
||||
className="PluginCardLink ButtonFilled"
|
||||
href={documentation}
|
||||
>
|
||||
Explore
|
||||
</a>
|
||||
</span>
|
||||
</Container>
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
<div className="PluginCard" id="add-plugin-card">
|
||||
<div className="PluginCardBody">
|
||||
<p>
|
||||
Do you have an existing plugin that you want to add to the
|
||||
Marketplace?
|
||||
</p>
|
||||
<p
|
||||
style={{
|
||||
marginTop: '20px',
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
<a className="ButtonFilled" href={addPluginDocsLink}>
|
||||
<b>Add to marketplace</b>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<Container className="PluginCardFooter">
|
||||
<p>
|
||||
See what plugins are already{' '}
|
||||
<a href="https://github.com/spotify/backstage/issues?q=is%3Aissue+is%3Aopen+label%3Aplugin+sort%3Areactions-%2B1-desc">
|
||||
in progress
|
||||
</a>{' '}
|
||||
and 👍. Missing a plugin for your favorite tool? Please{' '}
|
||||
<a href="https://github.com/spotify/backstage/issues/new?labels=plugin&template=plugin_template.md&title=%5BPlugin%5D+THE+PLUGIN+NAME">
|
||||
suggest
|
||||
</a>{' '}
|
||||
a new one.
|
||||
</p>
|
||||
</Container>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
|
||||
module.exports = Plugins;
|
||||
@@ -74,6 +74,7 @@
|
||||
"plugins/create-a-plugin",
|
||||
"plugins/plugin-development",
|
||||
"plugins/structure-of-a-plugin",
|
||||
"plugins/integrating-plugin-into-service-catalog",
|
||||
{
|
||||
"type": "subcategory",
|
||||
"label": "Backends and APIs",
|
||||
@@ -91,7 +92,7 @@
|
||||
{
|
||||
"type": "subcategory",
|
||||
"label": "Publishing",
|
||||
"ids": ["plugins/publishing", "plugins/publish-private"]
|
||||
"ids": ["plugins/publishing", "plugins/publish-private", "plugins/add-to-marketplace"]
|
||||
}
|
||||
],
|
||||
"Configuration": [
|
||||
|
||||
@@ -17,7 +17,7 @@ const siteConfig = {
|
||||
url: 'https://backstage.io', // Your website URL
|
||||
cname: 'backstage.io',
|
||||
baseUrl: '/', // Base URL for your project */
|
||||
editUrl: 'https://github.com/spotify/backstage/tree/master/docs/',
|
||||
editUrl: 'https://github.com/spotify/backstage/edit/master/docs/',
|
||||
|
||||
// Used for publishing and more
|
||||
projectName: 'backstage',
|
||||
@@ -38,6 +38,10 @@ const siteConfig = {
|
||||
href: '/docs',
|
||||
label: 'Docs',
|
||||
},
|
||||
{
|
||||
page: 'plugins',
|
||||
label: 'Plugins',
|
||||
},
|
||||
{
|
||||
page: 'blog',
|
||||
blog: true,
|
||||
|
||||
@@ -216,7 +216,7 @@ td {
|
||||
code {
|
||||
font-family: IBM Plex Mono, Menlo, Monaco, Consolas, Courier New, monospace;
|
||||
font-weight: 500;
|
||||
background-color: #0e0e0e;
|
||||
background-color: #272822;
|
||||
}
|
||||
|
||||
/* .stripe {
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
.PluginCard {
|
||||
background-color: #272822;
|
||||
height: 100%;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-gap: 1rem;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
grid-auto-rows: 1fr;
|
||||
padding-top: 32px;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 815px) {
|
||||
.grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.PluginCard img {
|
||||
float: left;
|
||||
margin: 0px 16px 8px 0px;
|
||||
height: 80px;
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.PluginCardHeader {
|
||||
max-height: fit-content;
|
||||
min-height: fit-content;
|
||||
}
|
||||
|
||||
.PluginCardTitle {
|
||||
color: white;
|
||||
vertical-align: top;
|
||||
margin: 8px 0px 0px 16px;
|
||||
}
|
||||
|
||||
.PluginAddNewButton {
|
||||
position: absolute;
|
||||
bottom: 16px;
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
.ButtonFilled {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
background-color: #36baa2;
|
||||
color: white;
|
||||
margin-top: 36px;
|
||||
}
|
||||
|
||||
.ButtonFilled:hover {
|
||||
border: 1px solid #36baa2;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.ChipOutlined {
|
||||
font-size: small;
|
||||
border-radius: 16px;
|
||||
padding: 2px 8px;
|
||||
border: 1px solid #36baa2;
|
||||
color: #36baa2;
|
||||
}
|
||||
|
||||
.PluginCardLink {
|
||||
padding: 2px 8px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.PluginPageLayout {
|
||||
margin: auto;
|
||||
max-width: 1430px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.PluginPageHeader {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.PluginPageHeader h2 {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.PluginCardBody {
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.PluginCardFooter {
|
||||
position: relative;
|
||||
min-height: 2em;
|
||||
}
|
||||
|
||||
.Author,
|
||||
.Author a {
|
||||
margin-bottom: 0.25em;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
.Author a:hover {
|
||||
color: white;
|
||||
}
|
||||
|
||||
#add-plugin-card {
|
||||
border: 1px solid #36baa2;
|
||||
}
|
||||
@@ -28,6 +28,7 @@ nav:
|
||||
- Overview: 'features/software-catalog/index.md'
|
||||
- System model: 'features/software-catalog/system-model.md'
|
||||
- YAML File Format: 'features/software-catalog/descriptor-format.md'
|
||||
- Configuration: 'features/software-catalog/configuration.md'
|
||||
- Extending the model: 'features/software-catalog/extending-the-model.md'
|
||||
- External integrations: 'features/software-catalog/external-integrations.md'
|
||||
- API: 'features/software-catalog/api.md'
|
||||
|
||||
+24
-23
@@ -1,30 +1,31 @@
|
||||
{
|
||||
"name": "example-app",
|
||||
"version": "0.1.1-alpha.20",
|
||||
"version": "0.1.1-alpha.21",
|
||||
"private": true,
|
||||
"bundled": true,
|
||||
"dependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.20",
|
||||
"@backstage/core": "^0.1.1-alpha.20",
|
||||
"@backstage/plugin-api-docs": "^0.1.1-alpha.20",
|
||||
"@backstage/plugin-catalog": "^0.1.1-alpha.20",
|
||||
"@backstage/plugin-circleci": "^0.1.1-alpha.20",
|
||||
"@backstage/plugin-explore": "^0.1.1-alpha.20",
|
||||
"@backstage/plugin-gcp-projects": "^0.1.1-alpha.20",
|
||||
"@backstage/plugin-github-actions": "^0.1.1-alpha.20",
|
||||
"@backstage/plugin-gitops-profiles": "^0.1.1-alpha.20",
|
||||
"@backstage/plugin-graphiql": "^0.1.1-alpha.20",
|
||||
"@backstage/plugin-jenkins": "^0.1.1-alpha.20",
|
||||
"@backstage/plugin-lighthouse": "^0.1.1-alpha.20",
|
||||
"@backstage/plugin-newrelic": "^0.1.1-alpha.20",
|
||||
"@backstage/plugin-register-component": "^0.1.1-alpha.20",
|
||||
"@backstage/plugin-rollbar": "^0.1.1-alpha.20",
|
||||
"@backstage/plugin-scaffolder": "^0.1.1-alpha.20",
|
||||
"@backstage/plugin-sentry": "^0.1.1-alpha.20",
|
||||
"@backstage/plugin-tech-radar": "^0.1.1-alpha.20",
|
||||
"@backstage/plugin-techdocs": "^0.1.1-alpha.20",
|
||||
"@backstage/plugin-welcome": "^0.1.1-alpha.20",
|
||||
"@backstage/test-utils": "^0.1.1-alpha.20",
|
||||
"@backstage/theme": "^0.1.1-alpha.20",
|
||||
"@backstage/cli": "^0.1.1-alpha.21",
|
||||
"@backstage/catalog-model": "^0.1.1-alpha.21",
|
||||
"@backstage/core": "^0.1.1-alpha.21",
|
||||
"@backstage/plugin-api-docs": "^0.1.1-alpha.21",
|
||||
"@backstage/plugin-catalog": "^0.1.1-alpha.21",
|
||||
"@backstage/plugin-circleci": "^0.1.1-alpha.21",
|
||||
"@backstage/plugin-explore": "^0.1.1-alpha.21",
|
||||
"@backstage/plugin-github-actions": "^0.1.1-alpha.21",
|
||||
"@backstage/plugin-gitops-profiles": "^0.1.1-alpha.21",
|
||||
"@backstage/plugin-graphiql": "^0.1.1-alpha.21",
|
||||
"@backstage/plugin-jenkins": "^0.1.1-alpha.21",
|
||||
"@backstage/plugin-lighthouse": "^0.1.1-alpha.21",
|
||||
"@backstage/plugin-newrelic": "^0.1.1-alpha.21",
|
||||
"@backstage/plugin-register-component": "^0.1.1-alpha.21",
|
||||
"@backstage/plugin-rollbar": "^0.1.1-alpha.21",
|
||||
"@backstage/plugin-scaffolder": "^0.1.1-alpha.21",
|
||||
"@backstage/plugin-sentry": "^0.1.1-alpha.21",
|
||||
"@backstage/plugin-tech-radar": "^0.1.1-alpha.21",
|
||||
"@backstage/plugin-techdocs": "^0.1.1-alpha.21",
|
||||
"@backstage/plugin-welcome": "^0.1.1-alpha.21",
|
||||
"@backstage/test-utils": "^0.1.1-alpha.21",
|
||||
"@backstage/theme": "^0.1.1-alpha.21",
|
||||
"@material-ui/core": "^4.9.1",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@octokit/rest": "^18.0.0",
|
||||
|
||||
@@ -27,6 +27,9 @@ describe('App', () => {
|
||||
data: {
|
||||
app: { title: 'Test' },
|
||||
backend: { baseUrl: 'http://localhost:7000' },
|
||||
lighthouse: {
|
||||
baseUrl: 'http://localhost:3003',
|
||||
},
|
||||
techdocs: {
|
||||
storageUrl: 'http://localhost:7000/techdocs/static/docs',
|
||||
},
|
||||
|
||||
@@ -26,6 +26,10 @@ import * as plugins from './plugins';
|
||||
import { apis } from './apis';
|
||||
import { hot } from 'react-hot-loader/root';
|
||||
import { providers } from './identityProviders';
|
||||
import { Router as CatalogRouter } from '@backstage/plugin-catalog';
|
||||
import { Route, Routes, Navigate } from 'react-router';
|
||||
|
||||
import { EntityPage } from './components/catalog/EntityPage';
|
||||
|
||||
const app = createApp({
|
||||
apis,
|
||||
@@ -46,7 +50,18 @@ const app = createApp({
|
||||
|
||||
const AppProvider = app.getProvider();
|
||||
const AppRouter = app.getRouter();
|
||||
const AppRoutes = app.getRoutes();
|
||||
const deprecatedAppRoutes = app.getRoutes();
|
||||
|
||||
const AppRoutes = () => (
|
||||
<Routes>
|
||||
<Route
|
||||
path="/catalog/*"
|
||||
element={<CatalogRouter EntityPage={EntityPage} />}
|
||||
/>
|
||||
<Navigate key="/" to="/catalog" />
|
||||
{...deprecatedAppRoutes}
|
||||
</Routes>
|
||||
);
|
||||
|
||||
const App: FC<{}> = () => (
|
||||
<AppProvider>
|
||||
|
||||
@@ -118,7 +118,7 @@ export const apis = (config: ConfigApi) => {
|
||||
|
||||
builder.add(featureFlagsApiRef, new FeatureFlags());
|
||||
|
||||
builder.add(lighthouseApiRef, new LighthouseRestApi('http://localhost:3003'));
|
||||
builder.add(lighthouseApiRef, LighthouseRestApi.fromConfig(config));
|
||||
|
||||
builder.add(travisCIApiRef, new TravisCIApi());
|
||||
builder.add(githubPullRequestsApiRef, new GithubPullRequestsClient());
|
||||
|
||||
@@ -90,7 +90,7 @@ const Root: FC<{}> = ({ children }) => (
|
||||
<SidebarSearchField onSearch={handleSearch} />
|
||||
<SidebarDivider />
|
||||
{/* Global nav, not org-specific */}
|
||||
<SidebarItem icon={HomeIcon} to="./" text="Home" />
|
||||
<SidebarItem icon={HomeIcon} to="/catalog" text="Home" />
|
||||
<SidebarItem icon={ExploreIcon} to="explore" text="Explore" />
|
||||
<SidebarItem icon={ExtensionIcon} to="api-docs" text="APIs" />
|
||||
<SidebarItem icon={LibraryBooks} to="docs" text="Docs" />
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Router as GitHubActionsRouter } from '@backstage/plugin-github-actions';
|
||||
import React from 'react';
|
||||
import {
|
||||
EntityPageLayout,
|
||||
useEntity,
|
||||
AboutCard,
|
||||
} from '@backstage/plugin-catalog';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { Grid } from '@material-ui/core';
|
||||
|
||||
const OverviewContent = ({ entity }: { entity: Entity }) => (
|
||||
<Grid container spacing={3}>
|
||||
<Grid item>
|
||||
<AboutCard entity={entity} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
|
||||
const ServiceEntityPage = ({ entity }: { entity: Entity }) => (
|
||||
<EntityPageLayout>
|
||||
<EntityPageLayout.Content
|
||||
path="/"
|
||||
title="Overview"
|
||||
element={<OverviewContent entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/ci-cd/*"
|
||||
title="CI/CD"
|
||||
element={<GitHubActionsRouter entity={entity} />}
|
||||
/>
|
||||
</EntityPageLayout>
|
||||
);
|
||||
|
||||
const WebsiteEntityPage = ({ entity }: { entity: Entity }) => (
|
||||
<EntityPageLayout>
|
||||
<EntityPageLayout.Content
|
||||
path="/"
|
||||
title="Overview"
|
||||
element={<OverviewContent entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/ci-cd/*"
|
||||
title="CI/CD"
|
||||
element={<GitHubActionsRouter entity={entity} />}
|
||||
/>
|
||||
</EntityPageLayout>
|
||||
);
|
||||
|
||||
const DefaultEntityPage = ({ entity }: { entity: Entity }) => (
|
||||
<EntityPageLayout>
|
||||
<EntityPageLayout.Content
|
||||
path="/*"
|
||||
title="Overview"
|
||||
element={<OverviewContent entity={entity} />}
|
||||
/>
|
||||
</EntityPageLayout>
|
||||
);
|
||||
|
||||
export const EntityPage = () => {
|
||||
const { entity } = useEntity();
|
||||
switch (entity?.spec?.type) {
|
||||
case 'service':
|
||||
return <ServiceEntityPage entity={entity} />;
|
||||
case 'website':
|
||||
return <WebsiteEntityPage entity={entity} />;
|
||||
default:
|
||||
return <DefaultEntityPage entity={entity} />;
|
||||
}
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/backend-common",
|
||||
"description": "Common functionality library for Backstage backends",
|
||||
"version": "0.1.1-alpha.20",
|
||||
"version": "0.1.1-alpha.21",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"private": false,
|
||||
@@ -29,9 +29,9 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/cli-common": "^0.1.1-alpha.20",
|
||||
"@backstage/config": "^0.1.1-alpha.20",
|
||||
"@backstage/config-loader": "^0.1.1-alpha.20",
|
||||
"@backstage/cli-common": "^0.1.1-alpha.21",
|
||||
"@backstage/config": "^0.1.1-alpha.21",
|
||||
"@backstage/config-loader": "^0.1.1-alpha.21",
|
||||
"@types/cors": "^2.8.6",
|
||||
"@types/express": "^4.17.6",
|
||||
"compression": "^1.7.4",
|
||||
@@ -46,7 +46,8 @@
|
||||
"prom-client": "^12.0.0",
|
||||
"selfsigned": "^1.10.7",
|
||||
"stoppable": "^1.1.0",
|
||||
"winston": "^3.2.1"
|
||||
"winston": "^3.2.1",
|
||||
"logform": "^2.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"pg-connection-string": "^2.3.0"
|
||||
@@ -57,7 +58,7 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.20",
|
||||
"@backstage/cli": "^0.1.1-alpha.21",
|
||||
"@types/compression": "^1.7.0",
|
||||
"@types/http-errors": "^1.6.3",
|
||||
"@types/morgan": "^1.9.0",
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { createDatabase } from './connection';
|
||||
import { createDatabaseClient } from './connection';
|
||||
|
||||
describe('database connection', () => {
|
||||
const createConfig = (data: any) =>
|
||||
@@ -26,10 +26,10 @@ describe('database connection', () => {
|
||||
},
|
||||
]);
|
||||
|
||||
describe(createDatabase, () => {
|
||||
describe(createDatabaseClient, () => {
|
||||
it('returns a postgres connection', () => {
|
||||
expect(
|
||||
createDatabase(
|
||||
createDatabaseClient(
|
||||
createConfig({
|
||||
client: 'pg',
|
||||
connection: {
|
||||
@@ -45,7 +45,7 @@ describe('database connection', () => {
|
||||
|
||||
it('returns an sqlite connection', () => {
|
||||
expect(
|
||||
createDatabase(
|
||||
createDatabaseClient(
|
||||
createConfig({
|
||||
client: 'sqlite3',
|
||||
connection: ':memory:',
|
||||
@@ -56,7 +56,7 @@ describe('database connection', () => {
|
||||
|
||||
it('tries to create a mysql connection as a passthrough', () => {
|
||||
expect(() =>
|
||||
createDatabase(
|
||||
createDatabaseClient(
|
||||
createConfig({
|
||||
client: 'mysql',
|
||||
connection: {
|
||||
@@ -72,7 +72,7 @@ describe('database connection', () => {
|
||||
|
||||
it('accepts overrides', () => {
|
||||
expect(
|
||||
createDatabase(
|
||||
createDatabaseClient(
|
||||
createConfig({
|
||||
client: 'pg',
|
||||
connection: {
|
||||
@@ -93,7 +93,7 @@ describe('database connection', () => {
|
||||
|
||||
it('throws an error without a client', () => {
|
||||
expect(() =>
|
||||
createDatabase(
|
||||
createDatabaseClient(
|
||||
createConfig({
|
||||
connection: '',
|
||||
}),
|
||||
@@ -103,7 +103,7 @@ describe('database connection', () => {
|
||||
|
||||
it('throws an error without a connection', () => {
|
||||
expect(() =>
|
||||
createDatabase(
|
||||
createDatabaseClient(
|
||||
createConfig({
|
||||
client: 'pg',
|
||||
}),
|
||||
|
||||
@@ -15,30 +15,36 @@
|
||||
*/
|
||||
|
||||
import knex from 'knex';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { Config } from '@backstage/config';
|
||||
import { mergeDatabaseConfig } from './config';
|
||||
import { createPgDatabase } from './postgres';
|
||||
import { createSqlite3Database } from './sqlite3';
|
||||
import { createPgDatabaseClient } from './postgres';
|
||||
import { createSqliteDatabaseClient } from './sqlite3';
|
||||
|
||||
type DatabaseClient = 'pg' | 'sqlite3' | string;
|
||||
|
||||
/**
|
||||
* Creates a knex database connection
|
||||
*
|
||||
* @param config The database config
|
||||
* @param dbConfig The database config
|
||||
* @param overrides Additional options to merge with the config
|
||||
*/
|
||||
export function createDatabase(
|
||||
config: ConfigReader,
|
||||
export function createDatabaseClient(
|
||||
dbConfig: Config,
|
||||
overrides?: Partial<knex.Config>,
|
||||
) {
|
||||
const client: DatabaseClient = config.getString('client');
|
||||
const client: DatabaseClient = dbConfig.getString('client');
|
||||
|
||||
if (client === 'pg') {
|
||||
return createPgDatabase(config, overrides);
|
||||
return createPgDatabaseClient(dbConfig, overrides);
|
||||
} else if (client === 'sqlite3') {
|
||||
return createSqlite3Database(config);
|
||||
return createSqliteDatabaseClient(dbConfig);
|
||||
}
|
||||
|
||||
return knex(mergeDatabaseConfig(config.get(), overrides));
|
||||
return knex(mergeDatabaseConfig(dbConfig.get(), overrides));
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for createDatabaseClient
|
||||
* @deprecated Use createDatabaseClient instead
|
||||
*/
|
||||
export const createDatabase = createDatabaseClient;
|
||||
|
||||
@@ -14,15 +14,26 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { Config, ConfigReader } from '@backstage/config';
|
||||
import {
|
||||
parsePgConnectionString,
|
||||
createPgDatabaseClient,
|
||||
buildPgDatabaseConfig,
|
||||
createPgDatabase,
|
||||
getPgConnectionConfig,
|
||||
parsePgConnectionString,
|
||||
} from './postgres';
|
||||
|
||||
describe('postgres', () => {
|
||||
const createConfig = (connection: any) =>
|
||||
const createMockConnection = () => ({
|
||||
host: 'acme',
|
||||
user: 'foo',
|
||||
password: 'bar',
|
||||
database: 'foodb',
|
||||
});
|
||||
|
||||
const createMockConnectionString = () =>
|
||||
'postgresql://foo:bar@acme:5432/foodb';
|
||||
|
||||
const createConfig = (connection: any): Config =>
|
||||
ConfigReader.fromConfigs([
|
||||
{
|
||||
context: '',
|
||||
@@ -35,86 +46,58 @@ describe('postgres', () => {
|
||||
|
||||
describe(buildPgDatabaseConfig, () => {
|
||||
it('builds a postgres config', () => {
|
||||
expect(
|
||||
buildPgDatabaseConfig(
|
||||
createConfig({
|
||||
host: 'acme',
|
||||
user: 'foo',
|
||||
password: 'bar',
|
||||
port: '5432',
|
||||
database: 'foodb',
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
const mockConnection = createMockConnection();
|
||||
|
||||
expect(buildPgDatabaseConfig(createConfig(mockConnection))).toEqual({
|
||||
client: 'pg',
|
||||
connection: {
|
||||
host: 'acme',
|
||||
user: 'foo',
|
||||
password: 'bar',
|
||||
port: '5432',
|
||||
database: 'foodb',
|
||||
},
|
||||
connection: mockConnection,
|
||||
useNullAsDefault: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('builds a connection string config', () => {
|
||||
expect(
|
||||
buildPgDatabaseConfig(
|
||||
createConfig('postgresql://foo:bar@acme:5432/foodb'),
|
||||
),
|
||||
).toEqual({
|
||||
client: 'pg',
|
||||
connection: 'postgresql://foo:bar@acme:5432/foodb',
|
||||
useNullAsDefault: true,
|
||||
});
|
||||
const mockConnectionString = createMockConnectionString();
|
||||
|
||||
expect(buildPgDatabaseConfig(createConfig(mockConnectionString))).toEqual(
|
||||
{
|
||||
client: 'pg',
|
||||
connection: mockConnectionString,
|
||||
useNullAsDefault: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('overrides the database name', () => {
|
||||
const mockConnection = createMockConnection();
|
||||
|
||||
expect(
|
||||
buildPgDatabaseConfig(
|
||||
createConfig({
|
||||
host: 'somehost',
|
||||
user: 'postgres',
|
||||
password: 'pass',
|
||||
database: 'foo',
|
||||
}),
|
||||
{ connection: { database: 'foodb' } },
|
||||
),
|
||||
buildPgDatabaseConfig(createConfig(mockConnection), {
|
||||
connection: { database: 'other_db' },
|
||||
}),
|
||||
).toEqual({
|
||||
client: 'pg',
|
||||
connection: {
|
||||
host: 'somehost',
|
||||
user: 'postgres',
|
||||
password: 'pass',
|
||||
database: 'foodb',
|
||||
...mockConnection,
|
||||
database: 'other_db',
|
||||
},
|
||||
useNullAsDefault: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('adds additional config settings', () => {
|
||||
const mockConnection = createMockConnection();
|
||||
|
||||
expect(
|
||||
buildPgDatabaseConfig(
|
||||
createConfig({
|
||||
host: 'somehost',
|
||||
user: 'postgres',
|
||||
password: 'pass',
|
||||
database: 'foo',
|
||||
}),
|
||||
{
|
||||
connection: { database: 'foodb' },
|
||||
pool: { min: 0, max: 7 },
|
||||
debug: true,
|
||||
},
|
||||
),
|
||||
buildPgDatabaseConfig(createConfig(mockConnection), {
|
||||
connection: { database: 'other_db' },
|
||||
pool: { min: 0, max: 7 },
|
||||
debug: true,
|
||||
}),
|
||||
).toEqual({
|
||||
client: 'pg',
|
||||
connection: {
|
||||
host: 'somehost',
|
||||
user: 'postgres',
|
||||
password: 'pass',
|
||||
database: 'foodb',
|
||||
...mockConnection,
|
||||
database: 'other_db',
|
||||
},
|
||||
useNullAsDefault: true,
|
||||
pool: { min: 0, max: 7 },
|
||||
@@ -123,37 +106,72 @@ describe('postgres', () => {
|
||||
});
|
||||
|
||||
it('overrides the database from connection string', () => {
|
||||
const mockConnectionString = createMockConnectionString();
|
||||
const mockConnection = createMockConnection();
|
||||
|
||||
expect(
|
||||
buildPgDatabaseConfig(
|
||||
createConfig('postgresql://postgres:pass@localhost:5432/dbname'),
|
||||
{ connection: { database: 'foodb' } },
|
||||
),
|
||||
buildPgDatabaseConfig(createConfig(mockConnectionString), {
|
||||
connection: { database: 'other_db' },
|
||||
}),
|
||||
).toEqual({
|
||||
client: 'pg',
|
||||
connection: {
|
||||
host: 'localhost',
|
||||
user: 'postgres',
|
||||
password: 'pass',
|
||||
...mockConnection,
|
||||
port: '5432',
|
||||
database: 'foodb',
|
||||
database: 'other_db',
|
||||
},
|
||||
useNullAsDefault: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(createPgDatabase, () => {
|
||||
describe(getPgConnectionConfig, () => {
|
||||
it('returns the connection object back', () => {
|
||||
const mockConnection = createMockConnection();
|
||||
const config = createConfig(mockConnection);
|
||||
|
||||
expect(getPgConnectionConfig(config)).toEqual(mockConnection);
|
||||
});
|
||||
|
||||
it('does not parse the connection string', () => {
|
||||
const mockConnection = createMockConnection();
|
||||
const config = createConfig(mockConnection);
|
||||
|
||||
expect(getPgConnectionConfig(config, true)).toEqual(mockConnection);
|
||||
});
|
||||
|
||||
it('automatically parses the connection string', () => {
|
||||
const mockConnection = createMockConnection();
|
||||
const mockConnectionString = createMockConnectionString();
|
||||
const config = createConfig(mockConnectionString);
|
||||
|
||||
expect(getPgConnectionConfig(config)).toEqual({
|
||||
...mockConnection,
|
||||
port: '5432',
|
||||
});
|
||||
});
|
||||
|
||||
it('parses the connection string', () => {
|
||||
const mockConnection = createMockConnection();
|
||||
const mockConnectionString = createMockConnectionString();
|
||||
const config = createConfig(mockConnectionString);
|
||||
|
||||
expect(getPgConnectionConfig(config, true)).toEqual({
|
||||
...mockConnection,
|
||||
port: '5432',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(createPgDatabaseClient, () => {
|
||||
it('creates a postgres knex instance', () => {
|
||||
expect(
|
||||
createPgDatabase(
|
||||
createPgDatabaseClient(
|
||||
createConfig({
|
||||
client: 'pg',
|
||||
connection: {
|
||||
host: 'acme',
|
||||
user: 'foo',
|
||||
password: 'bar',
|
||||
database: 'foodb',
|
||||
},
|
||||
host: 'acme',
|
||||
user: 'foo',
|
||||
password: 'bar',
|
||||
database: 'foodb',
|
||||
}),
|
||||
),
|
||||
).toBeTruthy();
|
||||
@@ -161,7 +179,7 @@ describe('postgres', () => {
|
||||
|
||||
it('attempts to read an ssl cert', () => {
|
||||
expect(() =>
|
||||
createPgDatabase(
|
||||
createPgDatabaseClient(
|
||||
createConfig(
|
||||
'postgresql://postgres:pass@localhost:5432/dbname?sslrootcert=/path/to/file',
|
||||
),
|
||||
|
||||
@@ -14,18 +14,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import knex from 'knex';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import knex, { PgConnectionConfig } from 'knex';
|
||||
import { Config } from '@backstage/config';
|
||||
import { mergeDatabaseConfig } from './config';
|
||||
|
||||
/**
|
||||
* Creates a knex sqlite3 database connection
|
||||
* Creates a knex postgres database connection
|
||||
*
|
||||
* @param dbConfig The database config
|
||||
* @param overrides Additional options to merge with the config
|
||||
*/
|
||||
export function createPgDatabase(
|
||||
dbConfig: ConfigReader,
|
||||
export function createPgDatabaseClient(
|
||||
dbConfig: Config,
|
||||
overrides?: knex.Config,
|
||||
) {
|
||||
const knexConfig = buildPgDatabaseConfig(dbConfig, overrides);
|
||||
@@ -40,26 +40,43 @@ export function createPgDatabase(
|
||||
* @param overrides Additional options to merge with the config
|
||||
*/
|
||||
export function buildPgDatabaseConfig(
|
||||
dbConfig: ConfigReader,
|
||||
dbConfig: Config,
|
||||
overrides?: knex.Config,
|
||||
) {
|
||||
const connection = dbConfig.get('connection') as any;
|
||||
|
||||
return mergeDatabaseConfig(
|
||||
dbConfig.get(),
|
||||
{
|
||||
// Only parse the connection string when overrides are provided
|
||||
connection:
|
||||
overrides &&
|
||||
(typeof connection === 'string' || connection instanceof String)
|
||||
? parsePgConnectionString(connection as string)
|
||||
: connection,
|
||||
connection: getPgConnectionConfig(dbConfig, !!overrides),
|
||||
useNullAsDefault: true,
|
||||
},
|
||||
overrides,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the postgres connection config
|
||||
*
|
||||
* @param dbConfig The database config
|
||||
* @param parseConnectionString Flag to explictly control connection string parsing
|
||||
*/
|
||||
export function getPgConnectionConfig(
|
||||
dbConfig: Config,
|
||||
parseConnectionString?: boolean,
|
||||
): PgConnectionConfig | string {
|
||||
const connection = dbConfig.get('connection') as any;
|
||||
const isConnectionString =
|
||||
typeof connection === 'string' || connection instanceof String;
|
||||
const autoParse = typeof parseConnectionString !== 'boolean';
|
||||
|
||||
const shouldParseConnectionString = autoParse
|
||||
? isConnectionString
|
||||
: parseConnectionString && isConnectionString;
|
||||
|
||||
return shouldParseConnectionString
|
||||
? parsePgConnectionString(connection as string)
|
||||
: connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a connection string using pg-connection-string
|
||||
*
|
||||
|
||||
@@ -15,7 +15,10 @@
|
||||
*/
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { buildSqlite3DatabaseConfig, createSqlite3Database } from './sqlite3';
|
||||
import {
|
||||
buildSqliteDatabaseConfig,
|
||||
createSqliteDatabaseClient,
|
||||
} from './sqlite3';
|
||||
|
||||
describe('sqlite3', () => {
|
||||
const createConfig = (connection: any) =>
|
||||
@@ -29,9 +32,9 @@ describe('sqlite3', () => {
|
||||
},
|
||||
]);
|
||||
|
||||
describe(buildSqlite3DatabaseConfig, () => {
|
||||
describe(buildSqliteDatabaseConfig, () => {
|
||||
it('buidls a string connection', () => {
|
||||
expect(buildSqlite3DatabaseConfig(createConfig(':memory:'))).toEqual({
|
||||
expect(buildSqliteDatabaseConfig(createConfig(':memory:'))).toEqual({
|
||||
client: 'sqlite3',
|
||||
connection: ':memory:',
|
||||
useNullAsDefault: true,
|
||||
@@ -40,7 +43,7 @@ describe('sqlite3', () => {
|
||||
|
||||
it('builds a filename connection', () => {
|
||||
expect(
|
||||
buildSqlite3DatabaseConfig(
|
||||
buildSqliteDatabaseConfig(
|
||||
createConfig({
|
||||
filename: '/path/to/foo',
|
||||
}),
|
||||
@@ -56,7 +59,7 @@ describe('sqlite3', () => {
|
||||
|
||||
it('replaces the connection with an override', () => {
|
||||
expect(
|
||||
buildSqlite3DatabaseConfig(createConfig(':memory:'), {
|
||||
buildSqliteDatabaseConfig(createConfig(':memory:'), {
|
||||
connection: { filename: '/path/to/foo' },
|
||||
}),
|
||||
).toEqual({
|
||||
@@ -69,10 +72,10 @@ describe('sqlite3', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe(createSqlite3Database, () => {
|
||||
describe(createSqliteDatabaseClient, () => {
|
||||
it('creates an in memory knex instance', () => {
|
||||
expect(
|
||||
createSqlite3Database(
|
||||
createSqliteDatabaseClient(
|
||||
createConfig({
|
||||
client: 'sqlite3',
|
||||
connection: ':memory:',
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import knex from 'knex';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { Config } from '@backstage/config';
|
||||
import { mergeDatabaseConfig } from './config';
|
||||
|
||||
/**
|
||||
@@ -24,11 +24,11 @@ import { mergeDatabaseConfig } from './config';
|
||||
* @param dbConfig The database config
|
||||
* @param overrides Additional options to merge with the config
|
||||
*/
|
||||
export function createSqlite3Database(
|
||||
dbConfig: ConfigReader,
|
||||
export function createSqliteDatabaseClient(
|
||||
dbConfig: Config,
|
||||
overrides?: knex.Config,
|
||||
) {
|
||||
const knexConfig = buildSqlite3DatabaseConfig(dbConfig, overrides);
|
||||
const knexConfig = buildSqliteDatabaseConfig(dbConfig, overrides);
|
||||
const database = knex(knexConfig);
|
||||
|
||||
database.client.pool.on('createSuccess', (_eventId: any, resource: any) => {
|
||||
@@ -44,8 +44,8 @@ export function createSqlite3Database(
|
||||
* @param dbConfig The database config
|
||||
* @param overrides Additional options to merge with the config
|
||||
*/
|
||||
export function buildSqlite3DatabaseConfig(
|
||||
dbConfig: ConfigReader,
|
||||
export function buildSqliteDatabaseConfig(
|
||||
dbConfig: Config,
|
||||
overrides?: knex.Config,
|
||||
) {
|
||||
return mergeDatabaseConfig(
|
||||
|
||||
@@ -20,4 +20,5 @@ export * from './errors';
|
||||
export * from './logging';
|
||||
export * from './middleware';
|
||||
export * from './service';
|
||||
export * from './paths';
|
||||
export * from './hot';
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import * as winston from 'winston';
|
||||
import { TransformableInfo } from 'logform';
|
||||
|
||||
const coloredTemplate = (info: TransformableInfo) => {
|
||||
const { timestamp, level, message, plugin, service } = info;
|
||||
const colorizer = winston.format.colorize();
|
||||
const prefix = plugin || service;
|
||||
const timestampColor = colorizer.colorize('timestamp', timestamp);
|
||||
const prefixColor = colorizer.colorize('prefix', prefix);
|
||||
|
||||
return `${timestampColor} ${prefixColor} ${level} ${message}`;
|
||||
};
|
||||
|
||||
export const coloredFormat = winston.format.combine(
|
||||
winston.format.timestamp(),
|
||||
winston.format.colorize({
|
||||
colors: { timestamp: 'dim', prefix: 'blue' },
|
||||
}),
|
||||
winston.format.printf(coloredTemplate),
|
||||
);
|
||||
@@ -14,17 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import * as winston from 'winston';
|
||||
import { coloredFormat } from './formats';
|
||||
|
||||
let rootLogger: winston.Logger = winston.createLogger({
|
||||
level: process.env.LOG_LEVEL || 'info',
|
||||
format:
|
||||
process.env.NODE_ENV === 'production'
|
||||
? winston.format.json()
|
||||
: winston.format.combine(
|
||||
winston.format.colorize(),
|
||||
winston.format.timestamp(),
|
||||
winston.format.simple(),
|
||||
),
|
||||
: coloredFormat,
|
||||
defaultMeta: { service: 'backstage' },
|
||||
transports: [
|
||||
new winston.transports.Console({
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
|
||||
import { resolve as resolvePath } from 'path';
|
||||
|
||||
/**
|
||||
* Resolve a path relative to the root of a package directory.
|
||||
* Additional path arguments are resolved relative to the package dir.
|
||||
*
|
||||
* This is particularly useful when you want to access assets shipped with
|
||||
* your backend plugin package. When doing so, do not forget to include the assets
|
||||
* in your published package by adding them to `files` in your `package.json`.
|
||||
*/
|
||||
export function resolvePackagePath(name: string, ...paths: string[]) {
|
||||
const req =
|
||||
typeof __non_webpack_require__ === 'undefined'
|
||||
? require
|
||||
: __non_webpack_require__;
|
||||
|
||||
return resolvePath(req.resolve(`${name}/package.json`), '..', ...paths);
|
||||
}
|
||||
@@ -135,7 +135,6 @@ export class ServiceBuilderImpl implements ServiceBuilder {
|
||||
app.use(cors(corsOptions));
|
||||
}
|
||||
app.use(compression());
|
||||
app.use(express.json());
|
||||
if (this.enableMetrics) {
|
||||
app.use(metricsHandler());
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ yarn start
|
||||
|
||||
Substitute `x` for actual values, or leave them as
|
||||
dummy values just to try out the backend without using the auth or sentry features.
|
||||
You can also, instead of using dummy values for a huge number of environment variables, remove those config directly from app-config.yaml file located in the root folder.
|
||||
|
||||
The backend starts up on port 7000 per default.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "example-backend",
|
||||
"version": "0.1.1-alpha.20",
|
||||
"version": "0.1.1-alpha.21",
|
||||
"main": "dist/index.cjs.js",
|
||||
"types": "src/index.ts",
|
||||
"private": true,
|
||||
@@ -18,20 +18,22 @@
|
||||
"migrate:create": "knex migrate:make -x ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.1.1-alpha.20",
|
||||
"@backstage/catalog-model": "^0.1.1-alpha.20",
|
||||
"@backstage/config": "^0.1.1-alpha.20",
|
||||
"@backstage/plugin-auth-backend": "^0.1.1-alpha.20",
|
||||
"@backstage/plugin-catalog-backend": "^0.1.1-alpha.20",
|
||||
"@backstage/plugin-graphql-backend": "^0.1.1-alpha.20",
|
||||
"@backstage/plugin-identity-backend": "^0.1.1-alpha.20",
|
||||
"@backstage/plugin-proxy-backend": "^0.1.1-alpha.20",
|
||||
"@backstage/plugin-rollbar-backend": "^0.1.1-alpha.20",
|
||||
"@backstage/plugin-scaffolder-backend": "^0.1.1-alpha.20",
|
||||
"@backstage/plugin-sentry-backend": "^0.1.1-alpha.20",
|
||||
"@backstage/plugin-techdocs-backend": "^0.1.1-alpha.20",
|
||||
"@backstage/backend-common": "^0.1.1-alpha.21",
|
||||
"@backstage/catalog-model": "^0.1.1-alpha.21",
|
||||
"@backstage/config": "^0.1.1-alpha.21",
|
||||
"@backstage/plugin-app-backend": "^0.1.1-alpha.21",
|
||||
"@backstage/plugin-auth-backend": "^0.1.1-alpha.21",
|
||||
"@backstage/plugin-catalog-backend": "^0.1.1-alpha.21",
|
||||
"@backstage/plugin-graphql-backend": "^0.1.1-alpha.21",
|
||||
"@backstage/plugin-identity-backend": "^0.1.1-alpha.21",
|
||||
"@backstage/plugin-proxy-backend": "^0.1.1-alpha.21",
|
||||
"@backstage/plugin-rollbar-backend": "^0.1.1-alpha.21",
|
||||
"@backstage/plugin-scaffolder-backend": "^0.1.1-alpha.21",
|
||||
"@backstage/plugin-sentry-backend": "^0.1.1-alpha.21",
|
||||
"@backstage/plugin-techdocs-backend": "^0.1.1-alpha.21",
|
||||
"@octokit/rest": "^18.0.0",
|
||||
"dockerode": "^3.2.0",
|
||||
"example-app": "^0.1.1-alpha.21",
|
||||
"express": "^4.17.1",
|
||||
"knex": "^0.21.1",
|
||||
"pg": "^8.3.0",
|
||||
@@ -40,10 +42,10 @@
|
||||
"winston": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.20",
|
||||
"@backstage/cli": "^0.1.1-alpha.21",
|
||||
"@types/dockerode": "^2.5.32",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/express-serve-static-core": "^4.17.5",
|
||||
"@types/helmet": "^0.0.47"
|
||||
"@types/helmet": "^0.0.48"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
createDatabase,
|
||||
createDatabaseClient,
|
||||
createServiceBuilder,
|
||||
loadBackendConfig,
|
||||
getRootLogger,
|
||||
@@ -40,6 +40,7 @@ import sentry from './plugins/sentry';
|
||||
import proxy from './plugins/proxy';
|
||||
import techdocs from './plugins/techdocs';
|
||||
import graphql from './plugins/graphql';
|
||||
import app from './plugins/app';
|
||||
import { PluginEnvironment } from './types';
|
||||
|
||||
function makeCreateEnv(loadedConfigs: AppConfig[]) {
|
||||
@@ -47,11 +48,14 @@ function makeCreateEnv(loadedConfigs: AppConfig[]) {
|
||||
|
||||
return (plugin: string): PluginEnvironment => {
|
||||
const logger = getRootLogger().child({ type: 'plugin', plugin });
|
||||
const database = createDatabase(config.getConfig('backend.database'), {
|
||||
connection: {
|
||||
database: `backstage_plugin_${plugin}`,
|
||||
const database = createDatabaseClient(
|
||||
config.getConfig('backend.database'),
|
||||
{
|
||||
connection: {
|
||||
database: `backstage_plugin_${plugin}`,
|
||||
},
|
||||
},
|
||||
});
|
||||
);
|
||||
return { logger, database, config };
|
||||
};
|
||||
}
|
||||
@@ -71,6 +75,7 @@ async function main() {
|
||||
const sentryEnv = useHotMemoize(module, () => createEnv('sentry'));
|
||||
const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs'));
|
||||
const graphqlEnv = useHotMemoize(module, () => createEnv('graphql'));
|
||||
const appEnv = useHotMemoize(module, () => createEnv('app'));
|
||||
|
||||
const service = createServiceBuilder(module)
|
||||
.loadConfig(configReader)
|
||||
@@ -82,8 +87,9 @@ async function main() {
|
||||
.addRouter('/auth', await auth(authEnv))
|
||||
.addRouter('/identity', await identity(identityEnv))
|
||||
.addRouter('/techdocs', await techdocs(techdocsEnv))
|
||||
.addRouter('/proxy', await proxy(proxyEnv))
|
||||
.addRouter('/graphql', await graphql(graphqlEnv));
|
||||
.addRouter('/proxy', await proxy(proxyEnv, '/proxy'))
|
||||
.addRouter('/graphql', await graphql(graphqlEnv))
|
||||
.addRouter('', await app(appEnv));
|
||||
|
||||
await service.start().catch(err => {
|
||||
console.log(err);
|
||||
|
||||
+8
-12
@@ -14,16 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { InfoCard, StructuredMetadataTable } from '@backstage/core';
|
||||
import React, { FC } from 'react';
|
||||
import { createRouter } from '@backstage/plugin-app-backend';
|
||||
import { PluginEnvironment } from '../types';
|
||||
|
||||
type Props = {
|
||||
entity: Entity;
|
||||
};
|
||||
|
||||
export const EntityMetadataCard: FC<Props> = ({ entity }) => (
|
||||
<InfoCard title="Information">
|
||||
<StructuredMetadataTable metadata={entity.metadata} />
|
||||
</InfoCard>
|
||||
);
|
||||
export default async function createPlugin({ logger }: PluginEnvironment) {
|
||||
return await createRouter({
|
||||
logger,
|
||||
appPackageName: 'example-app',
|
||||
});
|
||||
}
|
||||
@@ -13,13 +13,14 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// @ts-ignore
|
||||
import { createRouter } from '@backstage/plugin-proxy-backend';
|
||||
import { PluginEnvironment } from '../types';
|
||||
|
||||
export default async function createPlugin({
|
||||
logger,
|
||||
config,
|
||||
}: PluginEnvironment) {
|
||||
return await createRouter({ logger, config });
|
||||
export default async function createPlugin(
|
||||
{ logger, config }: PluginEnvironment,
|
||||
pathPrefix: string,
|
||||
) {
|
||||
return await createRouter({ logger, config, pathPrefix });
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/catalog-model",
|
||||
"version": "0.1.1-alpha.20",
|
||||
"version": "0.1.1-alpha.21",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -20,7 +20,7 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/config": "^0.1.1-alpha.20",
|
||||
"@backstage/config": "^0.1.1-alpha.21",
|
||||
"@types/json-schema": "^7.0.5",
|
||||
"@types/yup": "^0.28.2",
|
||||
"json-schema": "^0.2.5",
|
||||
@@ -29,7 +29,7 @@
|
||||
"yup": "^0.29.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.20",
|
||||
"@backstage/cli": "^0.1.1-alpha.21",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/jest": "^26.0.7",
|
||||
"@types/lodash": "^4.14.151",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/cli-common",
|
||||
"description": "Common functionality used by cli, backend, and create-app",
|
||||
"version": "0.1.1-alpha.20",
|
||||
"version": "0.1.1-alpha.21",
|
||||
"private": false,
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -28,6 +28,9 @@ module.exports = {
|
||||
env: {
|
||||
jest: true,
|
||||
},
|
||||
globals: {
|
||||
__non_webpack_require__: 'readonly',
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018,
|
||||
sourceType: 'module',
|
||||
|
||||
@@ -56,7 +56,12 @@ module.exports = {
|
||||
'@typescript-eslint/no-unused-expressions': 'error',
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'warn',
|
||||
{ vars: 'all', args: 'after-used', ignoreRestSiblings: true },
|
||||
{
|
||||
vars: 'all',
|
||||
args: 'after-used',
|
||||
ignoreRestSiblings: true,
|
||||
argsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
'no-restricted-imports': [
|
||||
2,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/cli",
|
||||
"description": "CLI for developing Backstage plugins and apps",
|
||||
"version": "0.1.1-alpha.20",
|
||||
"version": "0.1.1-alpha.21",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
@@ -28,9 +28,9 @@
|
||||
"backstage-cli": "bin/backstage-cli"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/cli-common": "^0.1.1-alpha.20",
|
||||
"@backstage/config": "^0.1.1-alpha.20",
|
||||
"@backstage/config-loader": "^0.1.1-alpha.20",
|
||||
"@backstage/cli-common": "^0.1.1-alpha.21",
|
||||
"@backstage/config": "^0.1.1-alpha.21",
|
||||
"@backstage/config-loader": "^0.1.1-alpha.21",
|
||||
"@hot-loader/react-dom": "^16.13.0",
|
||||
"@lerna/package-graph": "^3.18.5",
|
||||
"@lerna/project": "^3.18.0",
|
||||
@@ -40,17 +40,17 @@
|
||||
"@rollup/plugin-yaml": "^2.1.1",
|
||||
"@spotify/eslint-config": "^7.0.1",
|
||||
"@sucrase/webpack-loader": "^2.0.0",
|
||||
"@svgr/plugin-jsx": "4.3.x",
|
||||
"@svgr/plugin-jsx": "5.4.x",
|
||||
"@svgr/plugin-svgo": "4.3.x",
|
||||
"@svgr/rollup": "5.4.x",
|
||||
"@svgr/webpack": "4.3.x",
|
||||
"@svgr/webpack": "5.4.x",
|
||||
"@types/start-server-webpack-plugin": "^2.2.0",
|
||||
"@types/webpack-env": "^1.15.2",
|
||||
"@types/webpack-node-externals": "^2.5.0",
|
||||
"bfj": "^7.0.2",
|
||||
"chalk": "^4.0.0",
|
||||
"chokidar": "^3.3.1",
|
||||
"commander": "^4.1.1",
|
||||
"commander": "^6.1.0",
|
||||
"css-loader": "^3.5.3",
|
||||
"dashify": "^2.0.0",
|
||||
"diff": "^4.0.2",
|
||||
@@ -102,7 +102,7 @@
|
||||
"@types/fs-extra": "^9.0.1",
|
||||
"@types/html-webpack-plugin": "^3.2.2",
|
||||
"@types/http-proxy": "^1.17.4",
|
||||
"@types/inquirer": "^6.5.0",
|
||||
"@types/inquirer": "^7.3.1",
|
||||
"@types/mini-css-extract-plugin": "^0.9.1",
|
||||
"@types/node": "^13.7.2",
|
||||
"@types/ora": "^3.2.0",
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
} from '../../lib/codeowners';
|
||||
import { paths } from '../../lib/paths';
|
||||
import { Task, templatingTask } from '../../lib/tasks';
|
||||
import { version as backstageVersion } from '../../lib/version';
|
||||
|
||||
const exec = promisify(execCb);
|
||||
|
||||
@@ -239,7 +240,11 @@ export default async () => {
|
||||
await createTemporaryPluginFolder(tempDir);
|
||||
|
||||
Task.section('Preparing files');
|
||||
await templatingTask(templateDir, tempDir, { ...answers, version });
|
||||
await templatingTask(templateDir, tempDir, {
|
||||
...answers,
|
||||
version,
|
||||
backstageVersion,
|
||||
});
|
||||
|
||||
Task.section('Moving to final location');
|
||||
await movePlugin(tempDir, pluginDir, answers.id);
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
yesPromptFunc,
|
||||
} from '../../lib/diff';
|
||||
import { paths } from '../../lib/paths';
|
||||
import { version } from '../../lib/version';
|
||||
import { version as backstageVersion } from '../../lib/version';
|
||||
|
||||
export type PluginData = {
|
||||
id: string;
|
||||
@@ -62,9 +62,12 @@ export default async (cmd: Command) => {
|
||||
promptFunc = yesPromptFunc;
|
||||
}
|
||||
|
||||
const { version } = await fs.readJson(paths.resolveTargetRoot('lerna.json'));
|
||||
|
||||
const data = await readPluginData();
|
||||
const templateFiles = await diffTemplateFiles('default-plugin', {
|
||||
version,
|
||||
backstageVersion,
|
||||
...data,
|
||||
});
|
||||
await handleAllFiles(fileHandlers, templateFiles, promptFunc);
|
||||
|
||||
@@ -57,6 +57,12 @@ export default async (cmd: Command) => {
|
||||
}
|
||||
}
|
||||
|
||||
// This is the only thing that is not implemented by jest.run(), so we do it here instead
|
||||
// https://github.com/facebook/jest/blob/cd8828f7bbec6e55b4df5e41e853a5133c4a3ee1/packages/jest-cli/bin/jest.js#L12
|
||||
if (!process.env.NODE_ENV) {
|
||||
(process.env as any).NODE_ENV = 'test';
|
||||
}
|
||||
|
||||
// eslint-disable-next-line jest/no-jest-import
|
||||
await require('jest').run(args);
|
||||
};
|
||||
|
||||
@@ -88,15 +88,25 @@ export const makeConfigs = async (
|
||||
}),
|
||||
resolve({ mainFields }),
|
||||
commonjs({
|
||||
include: ['node_modules/**', '../../node_modules/**'],
|
||||
exclude: ['**/*.stories.*', '**/*.test.*'],
|
||||
include: /node_modules/,
|
||||
exclude: [/\/[^/]+\.(?:stories|test)\.[^/]+$/],
|
||||
}),
|
||||
postcss(),
|
||||
imageFiles({ exclude: '**/*.icon.svg' }),
|
||||
imageFiles({
|
||||
exclude: /\.icon\.svg$/,
|
||||
include: [
|
||||
/\.css$/,
|
||||
/\.svg$/,
|
||||
/\.png$/,
|
||||
/\.gif$/,
|
||||
/\.jpg$/,
|
||||
/\.jpeg$/,
|
||||
],
|
||||
}),
|
||||
json(),
|
||||
yaml(),
|
||||
svgr({
|
||||
include: '**/*.icon.svg',
|
||||
include: /\.icon\.svg$/,
|
||||
template: svgrTemplate,
|
||||
}),
|
||||
esbuild({
|
||||
|
||||
@@ -25,7 +25,7 @@ export async function serveBackend(
|
||||
},
|
||||
) {
|
||||
const paths = resolveBundlingPaths(options);
|
||||
const config = createBackendConfig(paths, {
|
||||
const config = await createBackendConfig(paths, {
|
||||
...options,
|
||||
isDev: true,
|
||||
});
|
||||
|
||||
@@ -36,7 +36,7 @@ export async function buildBundle(options: BuildOptions) {
|
||||
const { statsJsonEnabled } = options;
|
||||
|
||||
const paths = resolveBundlingPaths(options);
|
||||
const config = createConfig(paths, {
|
||||
const config = await createConfig(paths, {
|
||||
...options,
|
||||
checksEnabled: false,
|
||||
isDev: false,
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import fs from 'fs-extra';
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
|
||||
import HtmlWebpackPlugin from 'html-webpack-plugin';
|
||||
import ModuleScopePlugin from 'react-dev-utils/ModuleScopePlugin';
|
||||
@@ -25,6 +27,9 @@ import { Config } from '@backstage/config';
|
||||
import { BundlingPaths } from './paths';
|
||||
import { transforms } from './transforms';
|
||||
import { BundlingOptions, BackendBundlingOptions } from './types';
|
||||
import { version } from '../../lib/version';
|
||||
import { paths as cliPaths } from '../../lib/paths';
|
||||
import { runPlain } from '../run';
|
||||
|
||||
export function resolveBaseUrl(config: Config): URL {
|
||||
const baseUrl = config.getString('app.baseUrl');
|
||||
@@ -35,10 +40,40 @@ export function resolveBaseUrl(config: Config): URL {
|
||||
}
|
||||
}
|
||||
|
||||
export function createConfig(
|
||||
async function readBuildInfo() {
|
||||
const timestamp = Date.now();
|
||||
|
||||
let commit = 'unknown';
|
||||
try {
|
||||
commit = await runPlain('git', 'rev-parse', 'HEAD');
|
||||
} catch (error) {
|
||||
console.warn(`WARNING: Failed to read git commit, ${error}`);
|
||||
}
|
||||
|
||||
let gitVersion = 'unknown';
|
||||
try {
|
||||
gitVersion = await runPlain('git', 'describe', '--always');
|
||||
} catch (error) {
|
||||
console.warn(`WARNING: Failed to describe git version, ${error}`);
|
||||
}
|
||||
|
||||
const { version: packageVersion } = await fs.readJson(
|
||||
cliPaths.resolveTarget('package.json'),
|
||||
);
|
||||
|
||||
return {
|
||||
cliVersion: version,
|
||||
gitVersion,
|
||||
packageVersion,
|
||||
timestamp,
|
||||
commit,
|
||||
};
|
||||
}
|
||||
|
||||
export async function createConfig(
|
||||
paths: BundlingPaths,
|
||||
options: BundlingOptions,
|
||||
): webpack.Configuration {
|
||||
): Promise<webpack.Configuration> {
|
||||
const { checksEnabled, isDev } = options;
|
||||
|
||||
const { plugins, loaders } = transforms(options);
|
||||
@@ -81,6 +116,13 @@ export function createConfig(
|
||||
}),
|
||||
);
|
||||
|
||||
const buildInfo = await readBuildInfo();
|
||||
plugins.push(
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.BUILD_INFO': JSON.stringify(buildInfo),
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
mode: isDev ? 'development' : 'production',
|
||||
profile: false,
|
||||
@@ -130,14 +172,23 @@ export function createConfig(
|
||||
};
|
||||
}
|
||||
|
||||
export function createBackendConfig(
|
||||
export async function createBackendConfig(
|
||||
paths: BundlingPaths,
|
||||
options: BackendBundlingOptions,
|
||||
): webpack.Configuration {
|
||||
): Promise<webpack.Configuration> {
|
||||
const { checksEnabled, isDev } = options;
|
||||
|
||||
const { loaders } = transforms(options);
|
||||
|
||||
// Find all local monorepo packages and their node_modules, and mark them as external.
|
||||
const LernaProject = require('@lerna/project');
|
||||
const project = new LernaProject(cliPaths.targetDir);
|
||||
const packages = await project.getPackages();
|
||||
const localPackageNames = packages.map((p: any) => p.name);
|
||||
const moduleDirs = packages.map((p: any) =>
|
||||
resolvePath(p.location, 'node_modules'),
|
||||
);
|
||||
|
||||
return {
|
||||
mode: isDev ? 'development' : 'production',
|
||||
profile: false,
|
||||
@@ -152,11 +203,8 @@ export function createBackendConfig(
|
||||
externals: [
|
||||
nodeExternals({
|
||||
modulesDir: paths.rootNodeModules,
|
||||
allowlist: ['webpack/hot/poll?100', /\@backstage\/.*/],
|
||||
}),
|
||||
nodeExternals({
|
||||
modulesDir: paths.targetNodeModules,
|
||||
allowlist: ['webpack/hot/poll?100', /\@backstage\/.*/],
|
||||
additionalModuleDirs: moduleDirs,
|
||||
allowlist: ['webpack/hot/poll?100', ...localPackageNames],
|
||||
}),
|
||||
],
|
||||
target: 'node' as const,
|
||||
@@ -178,7 +226,7 @@ export function createBackendConfig(
|
||||
resolve: {
|
||||
extensions: ['.ts', '.tsx', '.mjs', '.js', '.jsx'],
|
||||
mainFields: ['browser', 'module', 'main'],
|
||||
modules: [paths.targetNodeModules, paths.rootNodeModules],
|
||||
modules: [paths.rootNodeModules, ...moduleDirs],
|
||||
plugins: [
|
||||
new ModuleScopePlugin(
|
||||
[paths.targetSrc, paths.targetDev],
|
||||
|
||||
@@ -63,7 +63,6 @@ export function resolveBundlingPaths(options: BundlingPathsOptions) {
|
||||
targetDev: paths.resolveTarget('dev'),
|
||||
targetEntry: resolveTargetModule(entry),
|
||||
targetTsConfig: paths.resolveTargetRoot('tsconfig.json'),
|
||||
targetNodeModules: paths.resolveTarget('node_modules'),
|
||||
targetPackageJson: paths.resolveTarget('package.json'),
|
||||
rootNodeModules: paths.resolveTargetRoot('node_modules'),
|
||||
root: paths.targetRoot,
|
||||
|
||||
@@ -30,7 +30,7 @@ export async function serveBundle(options: ServeOptions) {
|
||||
const paths = resolveBundlingPaths(options);
|
||||
const pkgPath = paths.targetPackageJson;
|
||||
const pkg = await fs.readJson(pkgPath);
|
||||
const config = createConfig(paths, {
|
||||
const config = await createConfig(paths, {
|
||||
...options,
|
||||
isDev: true,
|
||||
baseUrl: url,
|
||||
|
||||
@@ -26,6 +26,7 @@ type LernaPackage = {
|
||||
private: boolean;
|
||||
location: string;
|
||||
scripts: Record<string, string>;
|
||||
get(key: string): any;
|
||||
};
|
||||
|
||||
type FileEntry =
|
||||
@@ -107,6 +108,26 @@ async function moveToDistWorkspace(
|
||||
strip: 1,
|
||||
});
|
||||
await fs.remove(archivePath);
|
||||
|
||||
// We remove the dependencies from package.json of packages that are marked
|
||||
// as bundled, so that yarn doesn't try to install them.
|
||||
if (target.get('bundled')) {
|
||||
const pkgJson = await fs.readJson(
|
||||
resolvePath(absoluteOutputPath, 'package.json'),
|
||||
);
|
||||
delete pkgJson.dependencies;
|
||||
delete pkgJson.devDependencies;
|
||||
delete pkgJson.peerDependencies;
|
||||
delete pkgJson.optionalDependencies;
|
||||
|
||||
await fs.writeJson(
|
||||
resolvePath(absoluteOutputPath, 'package.json'),
|
||||
pkgJson,
|
||||
{
|
||||
spaces: 2,
|
||||
},
|
||||
);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core": "^{{version}}",
|
||||
"@backstage/theme": "^{{version}}",
|
||||
"@backstage/core": "^{{backstageVersion}}",
|
||||
"@backstage/theme": "^{{backstageVersion}}",
|
||||
"@material-ui/core": "^4.9.1",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
@@ -31,8 +31,8 @@
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^{{version}}",
|
||||
"@backstage/dev-utils": "^{{version}}",
|
||||
"@backstage/cli": "^{{backstageVersion}}",
|
||||
"@backstage/dev-utils": "^{{backstageVersion}}",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
"@testing-library/user-event": "^12.0.7",
|
||||
|
||||
@@ -2,13 +2,13 @@ import { createPlugin, createRouteRef } from '@backstage/core';
|
||||
import ExampleComponent from './components/ExampleComponent';
|
||||
|
||||
export const rootRouteRef = createRouteRef({
|
||||
path: '/{{ id }}',
|
||||
title: '{{ id }}',
|
||||
path: '/{{ id }}',
|
||||
title: '{{ id }}',
|
||||
});
|
||||
|
||||
export const plugin = createPlugin({
|
||||
id: '{{ id }}',
|
||||
register({ router }) {
|
||||
router.addRoute(rootRouteRef, ExampleComponent);
|
||||
},
|
||||
id: '{{ id }}',
|
||||
register({ router }) {
|
||||
router.addRoute(rootRouteRef, ExampleComponent);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/config-loader",
|
||||
"description": "Config loading functionality used by Backstage backend, and CLI",
|
||||
"version": "0.1.1-alpha.20",
|
||||
"version": "0.1.1-alpha.21",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -30,7 +30,7 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/config": "^0.1.1-alpha.20",
|
||||
"@backstage/config": "^0.1.1-alpha.21",
|
||||
"fs-extra": "^9.0.0",
|
||||
"yaml": "^1.9.2",
|
||||
"yup": "^0.29.1"
|
||||
|
||||
@@ -14,5 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { readEnvConfig } from './lib';
|
||||
export { loadConfig } from './loader';
|
||||
export type { LoadConfigOptions } from './loader';
|
||||
|
||||
@@ -14,16 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { readEnv } from './env';
|
||||
import { readEnvConfig } from './env';
|
||||
|
||||
describe('readEnv', () => {
|
||||
describe('readEnvConfig', () => {
|
||||
it('should return empty config for empty env', () => {
|
||||
expect(readEnv({})).toEqual([]);
|
||||
expect(readEnvConfig({})).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return empty config for no matching keys', () => {
|
||||
expect(
|
||||
readEnv({
|
||||
readEnvConfig({
|
||||
NODE_ENV: 'production',
|
||||
NOPE_ENV: 'development',
|
||||
APP_CONFIG: 'foo',
|
||||
@@ -34,7 +34,7 @@ describe('readEnv', () => {
|
||||
|
||||
it('should create config from env', () => {
|
||||
expect(
|
||||
readEnv({
|
||||
readEnvConfig({
|
||||
NODE_ENV: 'production',
|
||||
APP_CONFIG_foo: '"bar"',
|
||||
APP_CONFIG_numbers_a: '1',
|
||||
@@ -57,22 +57,22 @@ describe('readEnv', () => {
|
||||
});
|
||||
|
||||
it('should accept string values', () => {
|
||||
expect(readEnv({ APP_CONFIG_foo: '"abc"', APP_CONFIG_bar: 'xyz' })).toEqual(
|
||||
[
|
||||
{
|
||||
data: {
|
||||
foo: 'abc',
|
||||
bar: 'xyz',
|
||||
},
|
||||
context: 'env',
|
||||
expect(
|
||||
readEnvConfig({ APP_CONFIG_foo: '"abc"', APP_CONFIG_bar: 'xyz' }),
|
||||
).toEqual([
|
||||
{
|
||||
data: {
|
||||
foo: 'abc',
|
||||
bar: 'xyz',
|
||||
},
|
||||
],
|
||||
);
|
||||
context: 'env',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should accept complex objects', () => {
|
||||
expect(
|
||||
readEnv({
|
||||
readEnvConfig({
|
||||
APP_CONFIG_foo: '{ "a": 123, "b": "123", "c": [] }',
|
||||
APP_CONFIG_bar: '[123, "abc", {}]',
|
||||
}),
|
||||
@@ -95,7 +95,7 @@ describe('readEnv', () => {
|
||||
['APP_CONFIG_fo o'],
|
||||
['APP_CONFIG_foo_(foo)_foo'],
|
||||
])('should reject invalid key %p', key => {
|
||||
expect(() => readEnv({ [key]: '0' })).toThrow(
|
||||
expect(() => readEnvConfig({ [key]: '0' })).toThrow(
|
||||
`Invalid env config key '${key.replace('APP_CONFIG_', '')}'`,
|
||||
);
|
||||
});
|
||||
@@ -103,7 +103,7 @@ describe('readEnv', () => {
|
||||
it.each([['hello'], ['"hello'], ['{'], ['}']])(
|
||||
'should fallback to string when invalid json value %p',
|
||||
value => {
|
||||
expect(readEnv({ APP_CONFIG_foo: value })).toEqual([
|
||||
expect(readEnvConfig({ APP_CONFIG_foo: value })).toEqual([
|
||||
{
|
||||
data: {
|
||||
foo: value,
|
||||
@@ -116,7 +116,7 @@ describe('readEnv', () => {
|
||||
|
||||
it('should not allow null as a value', () => {
|
||||
expect(() =>
|
||||
readEnv({
|
||||
readEnvConfig({
|
||||
APP_CONFIG_foo: 'null',
|
||||
}),
|
||||
).toThrow(
|
||||
@@ -126,7 +126,7 @@ describe('readEnv', () => {
|
||||
|
||||
it('should not allow duplicate values', () => {
|
||||
expect(() =>
|
||||
readEnv({
|
||||
readEnvConfig({
|
||||
APP_CONFIG_foo_bar: '1',
|
||||
APP_CONFIG_foo_bar_baz: '2',
|
||||
}),
|
||||
@@ -137,7 +137,7 @@ describe('readEnv', () => {
|
||||
|
||||
it('should not allow mixing of objects and other values', () => {
|
||||
expect(() =>
|
||||
readEnv({
|
||||
readEnvConfig({
|
||||
APP_CONFIG_nested_foo: '1',
|
||||
APP_CONFIG_nested: '2',
|
||||
}),
|
||||
|
||||
@@ -39,7 +39,7 @@ const CONFIG_KEY_PART_PATTERN = /^[a-z][a-z0-9]*(?:[-_][a-z][a-z0-9]*)*$/i;
|
||||
*
|
||||
* APP_CONFIG_app_title='"My Title"'
|
||||
*/
|
||||
export function readEnv(env: {
|
||||
export function readEnvConfig(env: {
|
||||
[name: string]: string | undefined;
|
||||
}): AppConfig[] {
|
||||
let data: JsonObject | undefined = undefined;
|
||||
|
||||
@@ -16,5 +16,5 @@
|
||||
|
||||
export { resolveStaticConfig } from './resolver';
|
||||
export { readConfigFile } from './reader';
|
||||
export { readEnv } from './env';
|
||||
export { readEnvConfig } from './env';
|
||||
export { readSecret } from './secrets';
|
||||
|
||||
@@ -30,6 +30,14 @@ jest.mock('fs-extra', () => {
|
||||
sessionKey: development-key
|
||||
`,
|
||||
'/root/secrets/session-key.txt': 'abc123',
|
||||
'/secret-port/app-config.yaml': `
|
||||
backend:
|
||||
listen:
|
||||
port:
|
||||
$secret:
|
||||
file: secrets/port.txt
|
||||
`,
|
||||
'/secret-port/secrets/port.txt': '12345',
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -20,7 +20,7 @@ import { AppConfig, JsonObject } from '@backstage/config';
|
||||
import {
|
||||
resolveStaticConfig,
|
||||
readConfigFile,
|
||||
readEnv,
|
||||
readEnvConfig,
|
||||
readSecret,
|
||||
} from './lib';
|
||||
|
||||
@@ -102,7 +102,7 @@ export async function loadConfig(
|
||||
);
|
||||
}
|
||||
|
||||
configs.push(...readEnv(process.env));
|
||||
configs.push(...readEnvConfig(process.env));
|
||||
|
||||
return configs;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/config",
|
||||
"description": "Config API used by Backstage core, backend, and CLI",
|
||||
"version": "0.1.1-alpha.20",
|
||||
"version": "0.1.1-alpha.21",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
|
||||
@@ -88,7 +88,7 @@ function expectInvalidValues(config: ConfigReader) {
|
||||
"Invalid type in config for key 'string' in 'ctx', got string, wanted boolean",
|
||||
);
|
||||
expect(() => config.getNumber('string')).toThrow(
|
||||
"Invalid type in config for key 'string' in 'ctx', got string, wanted number",
|
||||
"Unable to convert config value for key 'string' in 'ctx' to a number",
|
||||
);
|
||||
expect(() => config.getString('one')).toThrow(
|
||||
"Invalid type in config for key 'one' in 'ctx', got number, wanted string",
|
||||
@@ -580,4 +580,17 @@ describe('ConfigReader.get()', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('coerces number strings to numbers', () => {
|
||||
const config = ConfigReader.fromConfigs([
|
||||
{
|
||||
data: {
|
||||
port: '123',
|
||||
},
|
||||
context: '1',
|
||||
},
|
||||
]);
|
||||
|
||||
expect(config.getNumber('port')).toEqual(123);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -49,6 +49,9 @@ const errors = {
|
||||
missing(key: string) {
|
||||
return `Missing required config value at '${key}'`;
|
||||
},
|
||||
convert(key: string, context: string, expected: string) {
|
||||
return `Unable to convert config value for key '${key}' in '${context}' to a ${expected}`;
|
||||
},
|
||||
};
|
||||
|
||||
export class ConfigReader implements Config {
|
||||
@@ -183,10 +186,22 @@ export class ConfigReader implements Config {
|
||||
}
|
||||
|
||||
getOptionalNumber(key: string): number | undefined {
|
||||
return this.readConfigValue(
|
||||
const value = this.readConfigValue<string | number>(
|
||||
key,
|
||||
value => typeof value === 'number' || { expected: 'number' },
|
||||
val =>
|
||||
typeof val === 'number' ||
|
||||
typeof val === 'string' || { expected: 'number' },
|
||||
);
|
||||
if (typeof value === 'number' || value === undefined) {
|
||||
return value;
|
||||
}
|
||||
const number = Number(value);
|
||||
if (!Number.isFinite(number)) {
|
||||
throw new Error(
|
||||
errors.convert(this.fullKey(key), this.context, 'number'),
|
||||
);
|
||||
}
|
||||
return number;
|
||||
}
|
||||
|
||||
getBoolean(key: string): boolean {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/core-api",
|
||||
"description": "Internal Core API used by Backstage plugins and apps",
|
||||
"version": "0.1.1-alpha.20",
|
||||
"version": "0.1.1-alpha.21",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -29,8 +29,8 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/config": "^0.1.1-alpha.20",
|
||||
"@backstage/theme": "^0.1.1-alpha.20",
|
||||
"@backstage/config": "^0.1.1-alpha.21",
|
||||
"@backstage/theme": "^0.1.1-alpha.21",
|
||||
"@material-ui/core": "^4.9.1",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@types/react": "^16.9",
|
||||
@@ -41,8 +41,8 @@
|
||||
"zen-observable": "^0.8.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.20",
|
||||
"@backstage/test-utils-core": "^0.1.1-alpha.20",
|
||||
"@backstage/cli": "^0.1.1-alpha.21",
|
||||
"@backstage/test-utils-core": "^0.1.1-alpha.21",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
"@testing-library/user-event": "^12.0.7",
|
||||
|
||||
@@ -136,7 +136,7 @@ export class PrivateAppImpl implements BackstageApp {
|
||||
return this.icons[key];
|
||||
}
|
||||
|
||||
getRoutes(): ComponentType<{}> {
|
||||
getRoutes(): JSX.Element[] {
|
||||
const routes = new Array<JSX.Element>();
|
||||
const registeredFeatureFlags = new Array<FeatureFlagsRegistryItem>();
|
||||
|
||||
@@ -191,14 +191,9 @@ export class PrivateAppImpl implements BackstageApp {
|
||||
FeatureFlags.registeredFeatureFlags = registeredFeatureFlags;
|
||||
}
|
||||
|
||||
const rendered = (
|
||||
<Routes>
|
||||
{routes}
|
||||
<Route element={<NotFoundErrorPage />} />
|
||||
</Routes>
|
||||
);
|
||||
routes.push(<Route element={<NotFoundErrorPage />} />);
|
||||
|
||||
return () => rendered;
|
||||
return routes;
|
||||
}
|
||||
|
||||
getProvider(): ComponentType<{}> {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user