Merge branch 'master' of github.com:spotify/backstage into shmidt-i/circle-ci-plugin-new-route-api
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
**/node_modules/**
|
||||
**/dist/**
|
||||
**/dist-types/**
|
||||
**/storybook-static/**
|
||||
**/coverage/**
|
||||
**/build/**
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
name: 'RFC'
|
||||
about: 'Request For Comments (RFC) from the community'
|
||||
labels: rfc
|
||||
title: '[RFC] <name>'
|
||||
---
|
||||
|
||||
**Status:** Open for comments
|
||||
|
||||
<!--- Open for comments |Closed for comments (RFC no longer maintained) --->
|
||||
|
||||
## Need
|
||||
|
||||
<!--- Why are we proposing this change? Why is this the problem we’re trying to address and what benefits/impact do we expect to get from this --->
|
||||
|
||||
## Proposal
|
||||
|
||||
<!--- The proposed approach. Describe the proposal in as much detail as needed for reviewers to give concrete feedback. Take special care in this section to describe any implications on data privacy or security. --->
|
||||
|
||||
## Alternatives
|
||||
|
||||
<!--- What alternatives to the proposed solution were considered? What criteria/data was used to discard these --->
|
||||
|
||||
## Risks
|
||||
|
||||
<!--- What other things happening could conflict or compete (for example for resources) with the proposal? What risk are there and how do we plan to handle them --->
|
||||
@@ -4,7 +4,9 @@
|
||||
That makes it easier to understand the change so we can :shipit: faster. -->
|
||||
|
||||
#### :heavy_check_mark: Checklist
|
||||
|
||||
<!--- Put an `x` in all the boxes that apply: -->
|
||||
|
||||
- [ ] All tests are passing `yarn test`
|
||||
- [ ] Screenshots attached (for UI changes)
|
||||
- [ ] Relevant documentation updated
|
||||
|
||||
@@ -69,11 +69,14 @@ jobs:
|
||||
- name: verify doc links
|
||||
run: node docs/verify-links.js
|
||||
|
||||
- name: prettier
|
||||
run: yarn prettier:check
|
||||
|
||||
- name: lint
|
||||
run: yarn lerna -- run lint --since origin/master
|
||||
|
||||
- name: type checking and declarations
|
||||
run: yarn tsc --incremental false
|
||||
run: yarn tsc:full
|
||||
|
||||
- name: build changed packages
|
||||
if: ${{ steps.yarn-lock.outcome == 'success' }}
|
||||
@@ -96,3 +99,16 @@ jobs:
|
||||
|
||||
- name: verify plugin template
|
||||
run: yarn lerna -- run diff -- --check
|
||||
|
||||
- name: ensure clean working directory
|
||||
run: |
|
||||
if files=$(git ls-files --exclude-standard --others --modified) && [[ -z "$files" ]]; then
|
||||
exit 0
|
||||
else
|
||||
echo ""
|
||||
echo "Working directory has been modified:"
|
||||
echo ""
|
||||
git status --short
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -49,6 +49,6 @@ jobs:
|
||||
|
||||
- run: yarn tsc
|
||||
- name: yarn build
|
||||
run: yarn build --ignore example-app --ignore example-backend --ignore @techdocs/cli --ignore backstage-microsite
|
||||
run: yarn build --ignore example-app --ignore example-backend --ignore @techdocs/cli
|
||||
- name: run E2E test
|
||||
run: yarn workspace e2e-test start
|
||||
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
|
||||
- run: yarn tsc
|
||||
- name: yarn build
|
||||
run: yarn build --ignore example-app --ignore example-backend --ignore @techdocs/cli --ignore backstage-microsite
|
||||
run: yarn build --ignore example-app --ignore example-backend --ignore @techdocs/cli
|
||||
- name: run E2E test
|
||||
run: |
|
||||
sudo sysctl fs.inotify.max_user_watches=524288
|
||||
|
||||
@@ -39,7 +39,22 @@ jobs:
|
||||
run: yarn install --frozen-lockfile
|
||||
# End of yarn setup
|
||||
|
||||
- name: lint
|
||||
run: yarn lerna -- run lint
|
||||
|
||||
# Tests are broken on Windows, disabled for now
|
||||
# - name: test
|
||||
# run: yarn lerna -- run test
|
||||
- name: type checking and declarations
|
||||
run: yarn tsc:full
|
||||
|
||||
- name: verify type dependencies
|
||||
run: yarn lint:type-deps
|
||||
|
||||
- name: test
|
||||
run: yarn lerna -- run test
|
||||
|
||||
- name: Discord notification
|
||||
if: ${{ failure() }}
|
||||
uses: Ilshidur/action-discord@0.2.0
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
with:
|
||||
args: 'Windows master build failed https://github.com/{{GITHUB_REPOSITORY}}/actions/runs/{{GITHUB_RUN_ID}}'
|
||||
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
run: yarn lerna -- run lint
|
||||
|
||||
- name: type checking and declarations
|
||||
run: yarn tsc --incremental false
|
||||
run: yarn tsc:full
|
||||
|
||||
- name: build
|
||||
run: yarn build
|
||||
@@ -71,9 +71,9 @@ jobs:
|
||||
# Tags the commit with the version in the core package if the tag doesn't exist
|
||||
- uses: Klemensas/action-autotag@1.2.3
|
||||
with:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
package_root: "packages/core"
|
||||
tag_prefix: "v"
|
||||
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
|
||||
package_root: 'packages/core'
|
||||
tag_prefix: 'v'
|
||||
|
||||
- name: Discord notification
|
||||
if: ${{ failure() }}
|
||||
|
||||
@@ -22,33 +22,17 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
# Beginning of yarn setup, keep in sync between all workflows, see ci.yml
|
||||
- name: use node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
registry-url: https://registry.npmjs.org/ # Needed for auth
|
||||
- name: cache all node_modules
|
||||
id: cache-modules
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: '**/node_modules'
|
||||
key: ${{ runner.os }}-node_modules-${{ hashFiles('yarn.lock', '**/package.json') }}
|
||||
- name: find location of global yarn cache
|
||||
id: yarn-cache
|
||||
if: steps.cache-modules.outputs.cache-hit != 'true'
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- name: cache global yarn cache
|
||||
uses: actions/cache@v2
|
||||
if: steps.cache-modules.outputs.cache-hit != 'true'
|
||||
with:
|
||||
path: ${{ steps.yarn-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
|
||||
# Skip caching of microsite dependencies, it keeps the global cache size
|
||||
# smaller, which make Windows builds a lot faster for the rest of the project.
|
||||
- name: yarn install
|
||||
run: yarn install --frozen-lockfile
|
||||
# End of yarn setup
|
||||
working-directory: microsite
|
||||
|
||||
- name: build microsite
|
||||
run: yarn workspace backstage-microsite build
|
||||
run: yarn build
|
||||
working-directory: microsite
|
||||
|
||||
@@ -26,36 +26,24 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
# Beginning of yarn setup, keep in sync between all workflows, see ci.yml
|
||||
- name: use node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
registry-url: https://registry.npmjs.org/ # Needed for auth
|
||||
- name: cache all node_modules
|
||||
id: cache-modules
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: '**/node_modules'
|
||||
key: ${{ runner.os }}-node_modules-${{ hashFiles('yarn.lock', '**/package.json') }}
|
||||
- name: find location of global yarn cache
|
||||
id: yarn-cache
|
||||
if: steps.cache-modules.outputs.cache-hit != 'true'
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- name: cache global yarn cache
|
||||
uses: actions/cache@v2
|
||||
if: steps.cache-modules.outputs.cache-hit != 'true'
|
||||
with:
|
||||
path: ${{ steps.yarn-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: yarn install
|
||||
|
||||
# We avoid caching in this workflow, as we're running an install of both the top-level
|
||||
# dependencies and the microsite. We leave it to the main master workflow to produce the
|
||||
# cache, as that results in a smaller bundle.
|
||||
- name: top-level yarn install
|
||||
run: yarn install --frozen-lockfile
|
||||
# End of yarn setup
|
||||
- name: microsite yarn install
|
||||
run: yarn install --frozen-lockfile
|
||||
working-directory: microsite
|
||||
|
||||
- name: build microsite
|
||||
run: yarn workspace backstage-microsite build
|
||||
run: yarn build
|
||||
working-directory: microsite
|
||||
|
||||
- name: build storybook
|
||||
run: yarn workspace storybook build-storybook
|
||||
|
||||
+1
-4
@@ -89,10 +89,7 @@ typings/
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Microsite build output
|
||||
microsite/build
|
||||
microsite/i18n
|
||||
dist-types
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
.yarn
|
||||
dist
|
||||
microsite/build
|
||||
coverage
|
||||
*.hbs
|
||||
templates
|
||||
plugins/scaffolder-backend/sample-templates
|
||||
.vscode
|
||||
+15
-16
@@ -4,12 +4,12 @@ This code of conduct outlines our expectations for participants within the **Spo
|
||||
|
||||
Our open source community strives to:
|
||||
|
||||
* **Be friendly and patient.**
|
||||
* **Be welcoming**: We strive to be a community that welcomes and supports people of all backgrounds and identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, colour, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, and mental and physical ability.
|
||||
* **Be considerate**: Your work will be used by other people, and you in turn will depend on the work of others. Any decision you take will affect users and colleagues, and you should take those consequences into account when making decisions. Remember that we're a world-wide community, so you might not be communicating in someone else's primary language.
|
||||
* **Be respectful**: Not all of us will agree all the time, but disagreement is no excuse for poor behavior and poor manners. We might all experience some frustration now and then, but we cannot allow that frustration to turn into a personal attack. It’s important to remember that a community where people feel uncomfortable or threatened is not a productive one.
|
||||
* **Be careful in the words that we choose**: we are a community of professionals, and we conduct ourselves professionally. Be kind to others. Do not insult or put down other participants. Harassment and other exclusionary behavior aren't acceptable.
|
||||
* **Try to understand why we disagree**: Disagreements, both social and technical, happen all the time. It is important that we resolve disagreements and differing views constructively. Remember that we’re different. The strength of our community comes from its diversity, people from a wide range of backgrounds. Different people have different perspectives on issues. Being unable to understand why someone holds a viewpoint doesn’t mean that they’re wrong. Don’t forget that it is human to err and blaming each other doesn’t get us anywhere. Instead, focus on helping to resolve issues and learning from mistakes.
|
||||
- **Be friendly and patient.**
|
||||
- **Be welcoming**: We strive to be a community that welcomes and supports people of all backgrounds and identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, colour, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, and mental and physical ability.
|
||||
- **Be considerate**: Your work will be used by other people, and you in turn will depend on the work of others. Any decision you take will affect users and colleagues, and you should take those consequences into account when making decisions. Remember that we're a world-wide community, so you might not be communicating in someone else's primary language.
|
||||
- **Be respectful**: Not all of us will agree all the time, but disagreement is no excuse for poor behavior and poor manners. We might all experience some frustration now and then, but we cannot allow that frustration to turn into a personal attack. It’s important to remember that a community where people feel uncomfortable or threatened is not a productive one.
|
||||
- **Be careful in the words that we choose**: we are a community of professionals, and we conduct ourselves professionally. Be kind to others. Do not insult or put down other participants. Harassment and other exclusionary behavior aren't acceptable.
|
||||
- **Try to understand why we disagree**: Disagreements, both social and technical, happen all the time. It is important that we resolve disagreements and differing views constructively. Remember that we’re different. The strength of our community comes from its diversity, people from a wide range of backgrounds. Different people have different perspectives on issues. Being unable to understand why someone holds a viewpoint doesn’t mean that they’re wrong. Don’t forget that it is human to err and blaming each other doesn’t get us anywhere. Instead, focus on helping to resolve issues and learning from mistakes.
|
||||
|
||||
## Definitions
|
||||
|
||||
@@ -18,7 +18,7 @@ Harassment includes, but is not limited to:
|
||||
- Offensive comments related to gender, gender identity and expression, sexual orientation, disability, mental illness, neuro(a)typicality, physical appearance, body size, race, age, regional discrimination, political or religious affiliation
|
||||
- Unwelcome comments regarding a person’s lifestyle choices and practices, including those related to food, health, parenting, drugs, and employment
|
||||
- Deliberate misgendering. This includes deadnaming or persistently using a pronoun that does not correctly reflect a person's gender identity. You must address people by the name they give you when not addressing them by their username or handle
|
||||
- Physical contact and simulated physical contact (eg, textual descriptions like “*hug*” or “*backrub*”) without consent or after a request to stop
|
||||
- Physical contact and simulated physical contact (eg, textual descriptions like “_hug_” or “_backrub_”) without consent or after a request to stop
|
||||
- Threats of violence, both physical and psychological
|
||||
- Incitement of violence towards any individual, including encouraging a person to commit suicide or to engage in self-harm
|
||||
- Deliberate intimidation
|
||||
@@ -39,7 +39,6 @@ Our open source community prioritizes marginalized people’s safety over privil
|
||||
- Communicating in a ‘tone’ you don’t find congenial
|
||||
- Criticizing racist, sexist, cissexist, or otherwise oppressive behavior or assumptions
|
||||
|
||||
|
||||
### Diversity Statement
|
||||
|
||||
We encourage everyone to participate and are committed to building a community for all. Although we will fail at times, we seek to treat everyone both as fairly and equally as possible. Whenever a participant has made a mistake, we expect them to take responsibility for it. If someone has been harmed or offended, it is our responsibility to listen carefully and respectfully, and do our best to right the wrong.
|
||||
@@ -53,18 +52,18 @@ If you experience or witness unacceptable behavior—or have any other concerns
|
||||
|
||||
- Your contact information.
|
||||
- Names (real, nicknames, or pseudonyms) of any individuals involved. If there are additional witnesses, please
|
||||
include them as well. Your account of what occurred, and if you believe the incident is ongoing. If there is a publicly available record (e.g. a mailing list archive or a public IRC logger), please include a link.
|
||||
include them as well. Your account of what occurred, and if you believe the incident is ongoing. If there is a publicly available record (e.g. a mailing list archive or a public IRC logger), please include a link.
|
||||
- Any additional information that may be helpful.
|
||||
|
||||
After filing a report, a representative will contact you personally, review the incident, follow up with any additional questions, and make a decision as to how to respond. If the person who is harassing you is part of the response team, they will recuse themselves from handling your incident. If the complaint originates from a member of the response team, it will be handled by a different member of the response team. We will respect confidentiality requests for the purpose of protecting victims of abuse.
|
||||
|
||||
### Attribution & Acknowledgements
|
||||
|
||||
We all stand on the shoulders of giants across many open source communities. We'd like to thank the communities and projects that established code of conducts and diversity statements as our inspiration:
|
||||
We all stand on the shoulders of giants across many open source communities. We'd like to thank the communities and projects that established code of conducts and diversity statements as our inspiration:
|
||||
|
||||
* [Django](https://www.djangoproject.com/conduct/reporting/)
|
||||
* [Python](https://www.python.org/community/diversity/)
|
||||
* [Ubuntu](http://www.ubuntu.com/about/about-ubuntu/conduct)
|
||||
* [Contributor Covenant](http://contributor-covenant.org/)
|
||||
* [Geek Feminism](http://geekfeminism.org/about/code-of-conduct/)
|
||||
* [Citizen Code of Conduct](http://citizencodeofconduct.org/)
|
||||
- [Django](https://www.djangoproject.com/conduct/reporting/)
|
||||
- [Python](https://www.python.org/community/diversity/)
|
||||
- [Ubuntu](http://www.ubuntu.com/about/about-ubuntu/conduct)
|
||||
- [Contributor Covenant](http://contributor-covenant.org/)
|
||||
- [Geek Feminism](http://geekfeminism.org/about/code-of-conduct/)
|
||||
- [Citizen Code of Conduct](http://citizencodeofconduct.org/)
|
||||
|
||||
+6
-1
@@ -1,9 +1,14 @@
|
||||
FROM nginx:mainline
|
||||
|
||||
# The purpose of this image is to serve the frontend app content separately.
|
||||
# By default the Backstage backend uses the app-backend plugin to serve the
|
||||
# app from the backend itself, but it may be desirable to move the frontend
|
||||
# content serving to a separate deployment, in which case this image can be used.
|
||||
|
||||
# This dockerfile requires the app to be built on the host first, as it
|
||||
# simply copies in the build output into the image.
|
||||
|
||||
# The safest way to build this image is to use `yarn docker-build`
|
||||
# The safest way to build this image is to use `yarn docker-build:app`
|
||||
|
||||
RUN apt-get update && apt-get -y install jq && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
app:
|
||||
baseUrl: http://localhost:3000
|
||||
|
||||
backend:
|
||||
baseUrl: http://localhost:7000
|
||||
listen:
|
||||
port: 7000
|
||||
cors:
|
||||
origin: http://localhost:3000
|
||||
methods: [GET, POST, PUT, DELETE]
|
||||
credentials: true
|
||||
+3
-7
@@ -1,15 +1,11 @@
|
||||
app:
|
||||
title: Backstage Example App
|
||||
baseUrl: http://localhost:3000
|
||||
baseUrl: http://localhost:7000
|
||||
|
||||
backend:
|
||||
baseUrl: http://localhost:7000
|
||||
listen:
|
||||
port: 7000
|
||||
cors:
|
||||
origin: http://localhost:3000
|
||||
methods: [GET, POST, PUT, DELETE]
|
||||
credentials: true
|
||||
database:
|
||||
client: sqlite3
|
||||
connection: ':memory:'
|
||||
@@ -130,8 +126,8 @@ auth:
|
||||
$secret:
|
||||
env: GITLAB_BASE_URL
|
||||
saml:
|
||||
entryPoint: "http://localhost:7001/"
|
||||
issuer: "passport-saml"
|
||||
entryPoint: 'http://localhost:7001/'
|
||||
issuer: 'passport-saml'
|
||||
okta:
|
||||
development:
|
||||
clientId:
|
||||
|
||||
+1
-2
@@ -3,10 +3,9 @@ kind: Component
|
||||
metadata:
|
||||
name: backstage
|
||||
description: |
|
||||
Backstage is an open-source developer portal that puts the developer experience first.
|
||||
Backstage is an open-source developer portal that puts the developer experience first.
|
||||
annotations:
|
||||
github.com/project-slug: spotify/backstage
|
||||
backstage.io/github-actions-id: spotify/backstage
|
||||
backstage.io/techdocs-ref: github:https://github.com/spotify/backstage.git
|
||||
spec:
|
||||
type: library
|
||||
|
||||
+2
-6
@@ -1,14 +1,10 @@
|
||||
# Make sure that before you
|
||||
# run the docker-compose that you have run
|
||||
# $ yarn docker-build:all
|
||||
# $ yarn docker-build
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
frontend:
|
||||
image: 'spotify/backstage:latest'
|
||||
ports:
|
||||
- '3000:80'
|
||||
backend:
|
||||
backstage:
|
||||
image: 'example-backend:latest'
|
||||
ports:
|
||||
- '7000:7000'
|
||||
|
||||
@@ -71,10 +71,9 @@ import {
|
||||
AlertApiForwarder,
|
||||
ErrorApiForwarder,
|
||||
ErrorAlerter,
|
||||
ConfigApi
|
||||
ConfigApi,
|
||||
} from '@backstage/core';
|
||||
|
||||
|
||||
const apis = (config: ConfigApi) => {
|
||||
const builder = ApiRegistry.builder();
|
||||
|
||||
@@ -84,7 +83,7 @@ const apis = (config: ConfigApi) => {
|
||||
// The error API uses the alert API to send error notifications to the user.
|
||||
builder.add(errorApiRef, new ErrorAlerter(alertApi, new ErrorApiForwarder()));
|
||||
return builder.build();
|
||||
}
|
||||
};
|
||||
|
||||
const app = createApp({
|
||||
apis,
|
||||
|
||||
+2
-2
@@ -116,8 +116,8 @@ components. If you’d like to help build up our design system, you can also add
|
||||
components we’ve designed to the Storybook as well.
|
||||
|
||||
**[Figma](https://www.figma.com/@backstage)** - we're stoked to be using Figma
|
||||
Community to share our design assets. You can duplicate our UI Kit
|
||||
and design your own plugin for Backstage.
|
||||
Community to share our design assets. You can duplicate our UI Kit and design
|
||||
your own plugin for Backstage.
|
||||
|
||||
**[Discord](https://discord.gg/EBHEGzX)** - all design questions should be
|
||||
directed to the _#design_ channel.
|
||||
|
||||
@@ -235,6 +235,9 @@ The `backstage.io/` prefix is reserved for use by Backstage core components.
|
||||
|
||||
Values can be of any length, but are limited to being strings.
|
||||
|
||||
There is a list of [well-known annotations](well-known-annotations.md), but
|
||||
anybody is free to add more annotations as they see fit.
|
||||
|
||||
### `tags` [optional]
|
||||
|
||||
A list of single-valued strings, for example to classify catalog entities in
|
||||
@@ -290,7 +293,7 @@ Exactly equal to `backstage.io/v1alpha1` and `Component`, respectively.
|
||||
|
||||
The type of component as a string, e.g. `website`. This field is required.
|
||||
|
||||
The software catalog accepts any type value, but an organisation should take
|
||||
The software catalog accepts any type value, but an organization should take
|
||||
great care to establish a proper taxonomy for these. Tools including Backstage
|
||||
itself may read this field and behave differently depending on its value. For
|
||||
example, a website type component may present tooling in the Backstage interface
|
||||
@@ -304,9 +307,9 @@ The current set of well-known and common values for this field is:
|
||||
|
||||
### `spec.lifecycle` [required]
|
||||
|
||||
The lifecyle state of the component, e.g. `production`. This field is required.
|
||||
The lifecycle state of the component, e.g. `production`. This field is required.
|
||||
|
||||
The software catalog accepts any lifecycle value, but an organisation should
|
||||
The software catalog accepts any lifecycle value, but an organization should
|
||||
take great care to establish a proper taxonomy for these.
|
||||
|
||||
The current set of well-known and common values for this field is:
|
||||
@@ -418,7 +421,7 @@ potentially search and group templates by these tags.
|
||||
The type of component as a string, e.g. `website`. This field is optional but
|
||||
recommended.
|
||||
|
||||
The software catalog accepts any type value, but an organisation should take
|
||||
The software catalog accepts any type value, but an organization should take
|
||||
great care to establish a proper taxonomy for these. Tools including Backstage
|
||||
itself may read this field and behave differently depending on its value. For
|
||||
example, a website type component may present tooling in the Backstage interface
|
||||
@@ -508,7 +511,7 @@ Exactly equal to `backstage.io/v1alpha1` and `API`, respectively.
|
||||
The type of the API definition as a string, e.g. `openapi`. This field is
|
||||
required.
|
||||
|
||||
The software catalog accepts any type value, but an organisation should take
|
||||
The software catalog accepts any type value, but an organization should take
|
||||
great care to establish a proper taxonomy for these. Tools including Backstage
|
||||
itself may read this field and behave differently depending on its value. For
|
||||
example, an OpenAPI type API may be displayed using an OpenAPI viewer tooling in
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
id: software-catalog-overview
|
||||
title: Backstage Service Catalog (alpha)
|
||||
sidebar_label: Backstage Service Catalog
|
||||
sidebar_label: Overview
|
||||
---
|
||||
|
||||
## What is a Service Catalog?
|
||||
@@ -48,7 +48,7 @@ There are 3 ways to add components to the catalog:
|
||||
|
||||
1. Manually register components
|
||||
2. Creating new components through Backstage
|
||||
3. Integrating with and [external source](external-integrations.md)
|
||||
3. Integrating with an [external source](external-integrations.md)
|
||||
|
||||
### Manually register components
|
||||
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
---
|
||||
id: well-known-annotations
|
||||
title: Well-known Annotations on Catalog Entities
|
||||
sidebar_label: Well-known Annotations
|
||||
---
|
||||
|
||||
This section lists a number of well known
|
||||
[annotations](descriptor-format.md#annotations-optional), that have defined
|
||||
semantics. They can be attached to catalog entities and consumed by plugins as
|
||||
needed.
|
||||
|
||||
## Annotations
|
||||
|
||||
This is a (non-exhaustive) list of annotations that are known to be in active
|
||||
use.
|
||||
|
||||
### backstage.io/managed-by-location
|
||||
|
||||
```yaml
|
||||
# Example:
|
||||
metadata:
|
||||
annotations:
|
||||
backstage.io/managed-by-location: github:http://github.com/spotify/backstage/catalog-info.yaml
|
||||
```
|
||||
|
||||
The value of this annotation is a so called location reference string, that
|
||||
points to the source from which the entity was originally fetched. This
|
||||
annotation is added automatically by the catalog as it fetches the data from a
|
||||
registered location, and is not meant to normally be written by humans. The
|
||||
annotation may point to any type of generic location that the catalog supports,
|
||||
so it cannot be relied on to always be specifically of type `github`, nor that
|
||||
it even represents a single file. Note also that a single location can be the
|
||||
source of many entities, so it represents a many-to-one relationship.
|
||||
|
||||
The format of the value is `<type>:<target>`. Note that the target may also
|
||||
contain colons, so it is not advisable to naively split the value on `:` and
|
||||
expecting a two-item array out of it. The format of the target part is
|
||||
type-dependent and could conceivably even be an empty string, but the separator
|
||||
colon is always present.
|
||||
|
||||
### backstage.io/techdocs-ref
|
||||
|
||||
```yaml
|
||||
# Example:
|
||||
metadata:
|
||||
annotations:
|
||||
backstage.io/techdocs-ref: github:https://github.com/spotify/backstage.git
|
||||
```
|
||||
|
||||
The value of this annotation is a location reference string (see above). If this
|
||||
annotation is specified, it is expected to point to a repository that the
|
||||
TechDocs system can read and generate docs from.
|
||||
|
||||
### backstage.io/jenkins-github-folder
|
||||
|
||||
```yaml
|
||||
# Example:
|
||||
metadata:
|
||||
annotations:
|
||||
backstage.io/jenkins-github-folder: folder-name/job-name
|
||||
```
|
||||
|
||||
The value of this annotation is the path to a job on Jenkins, that builds this
|
||||
entity.
|
||||
|
||||
Specifying this annotation may enable Jenkins related features in Backstage for
|
||||
that entity.
|
||||
|
||||
### github.com/project-slug
|
||||
|
||||
```yaml
|
||||
# Example:
|
||||
metadata:
|
||||
annotations:
|
||||
github.com/project-slug: spotify/backstage
|
||||
```
|
||||
|
||||
The value of this annotation is the so-called slug that identifies a project on
|
||||
[GitHub](https://github.com) that is related to this entity. It is on the format
|
||||
`<organization>/<project>`, and is the same as can be seen in the URL location
|
||||
bar of the browser when viewing that project.
|
||||
|
||||
Specifying this annotation will enable GitHub related features in Backstage for
|
||||
that entity.
|
||||
|
||||
### sentry.io/project-slug
|
||||
|
||||
```yaml
|
||||
# Example:
|
||||
metadata:
|
||||
annotations:
|
||||
sentry.io/project-slug: pump-station
|
||||
```
|
||||
|
||||
The value of this annotation is the so-called slug (or alternatively, the ID) of
|
||||
a [Sentry](https://sentry.io) project within your organization. The organization
|
||||
slug is currently not configurable on a per-entity basis, but is assumed to be
|
||||
the same for all entities in the catalog.
|
||||
|
||||
Specifying this annotation may enable Sentry related features in Backstage for
|
||||
that entity.
|
||||
|
||||
### rollbar.com/project-slug
|
||||
|
||||
```yaml
|
||||
# Example:
|
||||
metadata:
|
||||
annotations:
|
||||
rollbar.com/project-slug: spotify/pump-station
|
||||
```
|
||||
|
||||
The value of this annotation is the so-called slug (or alternatively, the ID) of
|
||||
a [Rollbar](https://rollbar.com) project within your organization. The value can
|
||||
be the format of `[organization]/[project-slug]` or just `[project-slug]`. When
|
||||
the organization slug is omitted the `app-config.yaml` will be used as a
|
||||
fallback (`rollbar.organization` followed by `organization.name`).
|
||||
|
||||
Specifying this annotation may enable Rollbar related features in Backstage for
|
||||
that entity.
|
||||
|
||||
## Deprecated Annotations
|
||||
|
||||
The following annotations are deprecated, and only listed here to aid in
|
||||
migrating away from them.
|
||||
|
||||
### backstage.io/github-actions-id
|
||||
|
||||
This annotation was used for a while to enable the GitHub Actions feature. This
|
||||
is now instead using the [github.com/project-slug](#github-com-project-slug)
|
||||
annotation, with the same value format.
|
||||
|
||||
## Links
|
||||
|
||||
- [Descriptor Format: annotations](descriptor-format.md#annotations-optional)
|
||||
@@ -7,18 +7,18 @@ 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 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.
|
||||
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.
|
||||
|
||||
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,
|
||||
but PR's are always welcome.
|
||||
`@spotify/plugin-scaffolder-backend`. Currently, only one publisher is
|
||||
supported, but PR's are always welcome.
|
||||
|
||||
An full example backend can be found
|
||||
[here](https://github.com/spotify/backstage/blob/d91c10f654475a60829fa33a5c81018e517a319a/packages/backend/src/plugins/scaffolder.ts),
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
---
|
||||
id: software-templates-index
|
||||
title: Software Templates
|
||||
title: Backstage Software Templates
|
||||
sidebar_label: Overview
|
||||
---
|
||||
|
||||
The Software Templates part of Backstage is a tool that can help you create
|
||||
Components inside Backstage. By default, it has the ability to load skeletons of
|
||||
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.
|
||||
|
||||
|
||||
@@ -30,9 +30,9 @@ the documentation template.
|
||||
Create an entity from the documentation template and you will get the needed
|
||||
setup for free.
|
||||
|
||||
!!! warning Currently the Backstage Software Templates are limited to create repositories
|
||||
inside GitHub organizations. You also need to generate an personal access token
|
||||
and use as an environment variable. Read more about this
|
||||
!!! warning Currently the Backstage Software Templates are limited to create
|
||||
repositories inside GitHub organizations. You also need to generate an personal
|
||||
access token and use as an environment variable. Read more about this
|
||||
[here](../software-templates/installation.md#runtime-dependencies).
|
||||
|
||||
### Manually add documentation setup to already existing repository
|
||||
|
||||
@@ -10,21 +10,20 @@ title: Other
|
||||
Run the following commands if you have Docker environment
|
||||
|
||||
```bash
|
||||
$ yarn install
|
||||
$ yarn docker-build
|
||||
$ docker run --rm -it -p 80:80 spotify/backstage
|
||||
$ docker run --rm -it -p 7000:7000 -e NODE_ENV=development example-backend:latest
|
||||
```
|
||||
|
||||
Then open http://localhost/ on your browser.
|
||||
|
||||
### Running with `docker-compose`
|
||||
|
||||
Run the following commands if you have docker and docker-compose for a full
|
||||
example, with the example backend also deployed.
|
||||
There is also a `docker-compose.yaml` that you can use to replace the previous
|
||||
`docker run` command:
|
||||
|
||||
```bash
|
||||
$ yarn docker-build:all
|
||||
$ yarn install
|
||||
$ yarn docker-build
|
||||
$ docker-compose up
|
||||
```
|
||||
|
||||
Then open http://localhost:3000 on your browser to see the example app with an
|
||||
example backend.
|
||||
|
||||
@@ -65,6 +65,7 @@ yarn storybook # Start local storybook, useful for working on components in @bac
|
||||
yarn workspace @backstage/plugin-welcome start # Serve welcome plugin only, also supports --check
|
||||
|
||||
yarn tsc # Run typecheck, use --watch for watch mode
|
||||
yarn tsc:full # Run full type checking, for example without skipLibCheck, use in CI
|
||||
|
||||
yarn build # Build published versions of packages, depends on tsc
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ 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.
|
||||
This is made easy with a version manager such as nvm which allows for version
|
||||
switching.
|
||||
|
||||
```bash
|
||||
# Checking your version
|
||||
|
||||
@@ -171,21 +171,19 @@ The frontend container can be built with a provided command.
|
||||
```bash
|
||||
yarn install
|
||||
yarn tsc
|
||||
yarn build
|
||||
yarn run docker-build
|
||||
yarn run docker-build:app
|
||||
```
|
||||
|
||||
Running this will simply generate a Docker container containing the contents of
|
||||
the UIs `dist` directory. The resulting container will be about 50MB in size.
|
||||
the UIs `dist` directory.
|
||||
|
||||
The backend container can be built by running the following command in the
|
||||
`packages/backend` directory.
|
||||
The backend container can be built by running the following command:
|
||||
|
||||
```bash
|
||||
yarn run build-image
|
||||
yarn run docker-build
|
||||
```
|
||||
|
||||
This will create a ~500MB container called `example-backend`.
|
||||
This will create a container called `example-backend`.
|
||||
|
||||
The lighthouse-audit-service container is already publicly available in Docker
|
||||
Hub and can be downloaded and ran with
|
||||
|
||||
@@ -18,10 +18,10 @@ spec:
|
||||
component: frontend
|
||||
spec:
|
||||
containers:
|
||||
- name: app
|
||||
image: spotify/backstage:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- containerPort: 80
|
||||
name: app
|
||||
protocol: TCP
|
||||
- name: app
|
||||
image: spotify/backstage:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- containerPort: 80
|
||||
name: app
|
||||
protocol: TCP
|
||||
|
||||
@@ -18,10 +18,10 @@ spec:
|
||||
component: backend
|
||||
spec:
|
||||
containers:
|
||||
- name: backend
|
||||
image: spotify/backstage-backend:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- containerPort: 7000
|
||||
name: backend
|
||||
protocol: TCP
|
||||
- name: backend
|
||||
image: spotify/backstage-backend:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- containerPort: 7000
|
||||
name: backend
|
||||
protocol: TCP
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
apiVersion: v1
|
||||
appVersion: "1.0"
|
||||
appVersion: '1.0'
|
||||
description: A Helm chart for Spotify Backstage
|
||||
name: backstage
|
||||
version: 0.1.1-alpha.12
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
app:
|
||||
enabled: true
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
nameOverride: ''
|
||||
fullnameOverride: ''
|
||||
replicaCount: 1
|
||||
serviceAccount:
|
||||
create: false
|
||||
Name: ""
|
||||
image:
|
||||
Name: ''
|
||||
image:
|
||||
repository: spotify/backstage
|
||||
tag: latest
|
||||
pullPolicy: Always
|
||||
@@ -15,20 +15,23 @@ app:
|
||||
port: 80
|
||||
ingress:
|
||||
enabled: false
|
||||
annotations: {}
|
||||
annotations:
|
||||
{}
|
||||
# kubernetes.io/ingress.class: "nginx"
|
||||
hosts:
|
||||
- host: backstage.local
|
||||
paths:
|
||||
- /
|
||||
- host: backstage.local
|
||||
paths:
|
||||
- /
|
||||
tls: []
|
||||
# - secretName: chart-example-tls
|
||||
# hosts:
|
||||
# - chart-example.local
|
||||
imagePullSecrets: []
|
||||
podSecurityContext: {}
|
||||
podSecurityContext:
|
||||
{}
|
||||
# fsGroup: 2000
|
||||
securityContext: {}
|
||||
securityContext:
|
||||
{}
|
||||
# capabilities:
|
||||
# drop:
|
||||
# - ALL
|
||||
@@ -48,12 +51,12 @@ app:
|
||||
|
||||
backend:
|
||||
enabled: false
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
nameOverride: ''
|
||||
fullnameOverride: ''
|
||||
replicaCount: 1
|
||||
serviceAccount:
|
||||
create: false
|
||||
Name: ""
|
||||
Name: ''
|
||||
image:
|
||||
repository: spotify/backstage-backend
|
||||
tag: latest
|
||||
@@ -63,20 +66,23 @@ backend:
|
||||
port: 7000
|
||||
ingress:
|
||||
enabled: false
|
||||
annotations: {}
|
||||
annotations:
|
||||
{}
|
||||
# kubernetes.io/ingress.class: "nginx"
|
||||
hosts:
|
||||
- host: backstage.local
|
||||
paths:
|
||||
- /backend
|
||||
- host: backstage.local
|
||||
paths:
|
||||
- /backend
|
||||
tls: []
|
||||
# - secretName: chart-example-tls
|
||||
# hosts:
|
||||
# - chart-example.local
|
||||
imagePullSecrets: []
|
||||
podSecurityContext: {}
|
||||
podSecurityContext:
|
||||
{}
|
||||
# fsGroup: 2000
|
||||
securityContext: {}
|
||||
securityContext:
|
||||
{}
|
||||
# capabilities:
|
||||
# drop:
|
||||
# - ALL
|
||||
|
||||
@@ -7,14 +7,14 @@ metadata:
|
||||
component: ingress
|
||||
spec:
|
||||
rules:
|
||||
- host: <HOSTNAME>
|
||||
http:
|
||||
paths:
|
||||
- backend:
|
||||
serviceName: backstage
|
||||
servicePort: frontend
|
||||
path: /
|
||||
- backend:
|
||||
serviceName: backstage-backend
|
||||
servicePort: backend
|
||||
path: /backend
|
||||
- host: <HOSTNAME>
|
||||
http:
|
||||
paths:
|
||||
- backend:
|
||||
serviceName: backstage
|
||||
servicePort: frontend
|
||||
path: /
|
||||
- backend:
|
||||
serviceName: backstage-backend
|
||||
servicePort: backend
|
||||
path: /backend
|
||||
|
||||
@@ -11,10 +11,10 @@ spec:
|
||||
app: backstage
|
||||
component: frontend
|
||||
ports:
|
||||
- name: frontend
|
||||
port: 80
|
||||
protocol: TCP
|
||||
targetPort: app
|
||||
- name: frontend
|
||||
port: 80
|
||||
protocol: TCP
|
||||
targetPort: app
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
@@ -29,7 +29,7 @@ spec:
|
||||
app: backstage
|
||||
component: backend
|
||||
ports:
|
||||
- name: backend
|
||||
port: 7000
|
||||
protocol: TCP
|
||||
targetPort: backend
|
||||
- name: backend
|
||||
port: 7000
|
||||
protocol: TCP
|
||||
targetPort: backend
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
# Build output
|
||||
build
|
||||
i18n
|
||||
+25
-1
@@ -2,12 +2,36 @@ This website was created with [Docusaurus](https://docusaurus.io/).
|
||||
|
||||
# What's In This Document
|
||||
|
||||
- [Get Started in 5 Minutes](#get-started-in-5-minutes)
|
||||
- [Getting Started](#getting-started)
|
||||
- [Directory Structure](#directory-structure)
|
||||
- [Editing Content](#editing-content)
|
||||
- [Adding Content](#adding-content)
|
||||
- [Full Documentation](#full-documentation)
|
||||
|
||||
# Getting Started
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
$ yarn install
|
||||
```
|
||||
|
||||
## Local Development
|
||||
|
||||
```
|
||||
$ yarn start
|
||||
```
|
||||
|
||||
This command starts a local development server and open up a browser window. Most changes are reflected live without having to restart the server.
|
||||
|
||||
## Build
|
||||
|
||||
```
|
||||
$ yarn build
|
||||
```
|
||||
|
||||
This command generates static content into the `build` directory, which is what will be deployed to GitHub pages from the master branch.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
Your project file structure should look something like this
|
||||
|
||||
@@ -7,4 +7,3 @@ 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'
|
||||
|
||||
|
||||
@@ -7,4 +7,3 @@ 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'
|
||||
|
||||
|
||||
@@ -7,4 +7,3 @@ 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,5 +1,5 @@
|
||||
{
|
||||
"version": "0.1.1-alpha.21",
|
||||
"version": "0.0.0",
|
||||
"name": "backstage-microsite",
|
||||
"license": "Apache-2.0",
|
||||
"private": true,
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"Features": [
|
||||
"Core Features": [
|
||||
{
|
||||
"type": "subcategory",
|
||||
"label": "Software Catalog",
|
||||
@@ -46,7 +46,7 @@
|
||||
},
|
||||
{
|
||||
"type": "subcategory",
|
||||
"label": "Software creation templates",
|
||||
"label": "Software Templates",
|
||||
"ids": [
|
||||
"features/software-templates/software-templates-index",
|
||||
"features/software-templates/adding-templates",
|
||||
@@ -92,7 +92,11 @@
|
||||
{
|
||||
"type": "subcategory",
|
||||
"label": "Publishing",
|
||||
"ids": ["plugins/publishing", "plugins/publish-private", "plugins/add-to-marketplace"]
|
||||
"ids": [
|
||||
"plugins/publishing",
|
||||
"plugins/publish-private",
|
||||
"plugins/add-to-marketplace"
|
||||
]
|
||||
}
|
||||
],
|
||||
"Configuration": [
|
||||
|
||||
+6650
File diff suppressed because it is too large
Load Diff
+5
-4
@@ -10,6 +10,7 @@
|
||||
"start-backend": "yarn workspace example-backend start",
|
||||
"build": "lerna run build",
|
||||
"tsc": "tsc",
|
||||
"tsc:full": "tsc --skipLibCheck false --incremental false",
|
||||
"clean": "backstage-cli clean && lerna run clean",
|
||||
"diff": "lerna run diff --",
|
||||
"test": "lerna run test --since origin/master -- --coverage",
|
||||
@@ -18,11 +19,12 @@
|
||||
"lint:all": "lerna run lint --",
|
||||
"lint:type-deps": "node scripts/check-type-dependencies.js",
|
||||
"docgen": "lerna run docgen",
|
||||
"docker-build": "yarn workspace example-app build && docker build . -t spotify/backstage",
|
||||
"docker-build:all": "yarn tsc && yarn build && yarn docker-build && yarn workspace example-backend build-image",
|
||||
"docker-build:app": "yarn workspace example-app build && docker build . -t spotify/backstage",
|
||||
"docker-build": "yarn tsc && yarn workspace example-backend build-image",
|
||||
"create-plugin": "backstage-cli create-plugin",
|
||||
"remove-plugin": "backstage-cli remove-plugin",
|
||||
"release": "if [ \"$(git symbolic-ref --short HEAD)\" = master ]; then echo \"don't try to release master\"; exit 1; else lerna version --no-push --force-publish; fi",
|
||||
"prettier:check": "prettier --check .",
|
||||
"lerna": "lerna",
|
||||
"storybook": "yarn workspace storybook start",
|
||||
"build-storybook": "yarn workspace storybook build-storybook"
|
||||
@@ -30,8 +32,7 @@
|
||||
"workspaces": {
|
||||
"packages": [
|
||||
"packages/*",
|
||||
"plugins/*",
|
||||
"microsite"
|
||||
"plugins/*"
|
||||
]
|
||||
},
|
||||
"version": "1.0.0",
|
||||
|
||||
@@ -27,6 +27,7 @@ import { apis } from './apis';
|
||||
import { hot } from 'react-hot-loader/root';
|
||||
import { providers } from './identityProviders';
|
||||
import { Router as CatalogRouter } from '@backstage/plugin-catalog';
|
||||
import { Router as DocsRouter } from '@backstage/plugin-techdocs';
|
||||
import { Route, Routes, Navigate } from 'react-router';
|
||||
|
||||
import { EntityPage } from './components/catalog/EntityPage';
|
||||
@@ -58,6 +59,7 @@ const AppRoutes = () => (
|
||||
path="/catalog/*"
|
||||
element={<CatalogRouter EntityPage={EntityPage} />}
|
||||
/>
|
||||
<Route path="/docs/*" element={<DocsRouter />} />
|
||||
<Navigate key="/" to="/catalog" />
|
||||
{...deprecatedAppRoutes}
|
||||
</Routes>
|
||||
|
||||
@@ -21,12 +21,14 @@ import {
|
||||
Router as CircleCIRouter,
|
||||
isPluginApplicableToEntity as isCircleCIAvailable,
|
||||
} from '@backstage/plugin-circleci';
|
||||
import { Router as ApiDocsRouter } from '@backstage/plugin-api-docs';
|
||||
import { Router as SentryRouter } from '@backstage/plugin-sentry';
|
||||
import { EmbeddedDocsRouter as DocsRouter } from '@backstage/plugin-techdocs';
|
||||
import React from 'react';
|
||||
import {
|
||||
AboutCard,
|
||||
EntityPageLayout,
|
||||
useEntity,
|
||||
AboutCard,
|
||||
} from '@backstage/plugin-catalog';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { Grid } from '@material-ui/core';
|
||||
@@ -75,6 +77,16 @@ const ServiceEntityPage = ({ entity }: { entity: Entity }) => (
|
||||
title="Sentry"
|
||||
element={<SentryRouter entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/api/*"
|
||||
title="API"
|
||||
element={<ApiDocsRouter entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/docs/*"
|
||||
title="Docs"
|
||||
element={<DocsRouter entity={entity} />}
|
||||
/>
|
||||
</EntityPageLayout>
|
||||
);
|
||||
|
||||
@@ -95,9 +107,13 @@ const WebsiteEntityPage = ({ entity }: { entity: Entity }) => (
|
||||
title="Sentry"
|
||||
element={<SentryRouter entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/docs/*"
|
||||
title="Docs"
|
||||
element={<DocsRouter entity={entity} />}
|
||||
/>
|
||||
</EntityPageLayout>
|
||||
);
|
||||
|
||||
const DefaultEntityPage = ({ entity }: { entity: Entity }) => (
|
||||
<EntityPageLayout>
|
||||
<EntityPageLayout.Content
|
||||
@@ -105,6 +121,11 @@ const DefaultEntityPage = ({ entity }: { entity: Entity }) => (
|
||||
title="Overview"
|
||||
element={<OverviewContent entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/docs/*"
|
||||
title="Docs"
|
||||
element={<DocsRouter entity={entity} />}
|
||||
/>
|
||||
</EntityPageLayout>
|
||||
);
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ export function requestLoggingHandler(logger?: Logger): RequestHandler {
|
||||
return morgan('combined', {
|
||||
stream: {
|
||||
write(message: String) {
|
||||
actualLogger.info(message);
|
||||
actualLogger.info(message.trimRight());
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2,10 +2,15 @@ FROM node:12
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Copy repo skeleton first, to avoid unnecessary docker cache invalidation.
|
||||
# The skeleton contains the package.json of each package in the monorepo,
|
||||
# and along with yarn.lock and the root package.json, that's enough to run yarn install.
|
||||
ADD yarn.lock package.json skeleton.tar ./
|
||||
|
||||
RUN yarn install --frozen-lockfile --production
|
||||
|
||||
# This will copy the contents of the dist-workspace when running the build-image command.
|
||||
# Do not use this Dockerfile outside of that command, as it will copy in the source code instead.
|
||||
COPY . .
|
||||
|
||||
RUN yarn install --frozen-lockfile --production
|
||||
|
||||
CMD ["node", "packages/backend"]
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"build": "backstage-cli backend:build",
|
||||
"build-image": "backstage-cli backend:build-image example-backend",
|
||||
"build-image": "backstage-cli backend:build-image --build --tag example-backend",
|
||||
"start": "backstage-cli backend:dev",
|
||||
"lint": "backstage-cli lint",
|
||||
"test": "backstage-cli test",
|
||||
|
||||
@@ -106,7 +106,10 @@ export function findOwnRootDir(ownDir: string) {
|
||||
*/
|
||||
export function findPaths(searchDir: string): Paths {
|
||||
const ownDir = findOwnDir(searchDir);
|
||||
const targetDir = fs.realpathSync(process.cwd());
|
||||
// Drive letter can end up being lowercased here on Windows, bring back to uppercase for consistency
|
||||
const targetDir = fs
|
||||
.realpathSync(process.cwd())
|
||||
.replace(/^[a-z]:/, str => str.toUpperCase());
|
||||
|
||||
// Lazy load this as it will throw an error if we're not inside the Backstage repo.
|
||||
let ownRoot = '';
|
||||
|
||||
@@ -35,7 +35,7 @@ module.exports = {
|
||||
ecmaVersion: 2018,
|
||||
sourceType: 'module',
|
||||
},
|
||||
ignorePatterns: ['.eslintrc.js', '**/dist/**'],
|
||||
ignorePatterns: ['.eslintrc.js', '**/dist/**', '**/dist-types/**'],
|
||||
rules: {
|
||||
'no-console': 0, // Permitted in console programs
|
||||
'new-cap': ['error', { capIsNew: false }], // Because Express constructs things e.g. like 'const r = express.Router()'
|
||||
|
||||
@@ -39,7 +39,7 @@ module.exports = {
|
||||
version: 'detect',
|
||||
},
|
||||
},
|
||||
ignorePatterns: ['.eslintrc.js', '**/dist/**'],
|
||||
ignorePatterns: ['.eslintrc.js', '**/dist/**', '**/dist-types/**'],
|
||||
rules: {
|
||||
'import/newline-after-import': 'error',
|
||||
'import/no-duplicates': 'warn',
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
"removeComments": false,
|
||||
"resolveJsonModule": true,
|
||||
"sourceMap": false,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"strictBindCallApply": true,
|
||||
"strictFunctionTypes": true,
|
||||
|
||||
@@ -15,28 +15,65 @@
|
||||
*/
|
||||
|
||||
import fs from 'fs-extra';
|
||||
import { join as joinPath, relative as relativePath } from 'path';
|
||||
import { createDistWorkspace } from '../../lib/packager';
|
||||
import { paths } from '../../lib/paths';
|
||||
import { run } from '../../lib/run';
|
||||
import { Command } from 'commander';
|
||||
|
||||
const PKG_PATH = 'package.json';
|
||||
|
||||
export default async (imageTag: string) => {
|
||||
export default async (cmd: Command) => {
|
||||
// Skip the preparation steps if we're being asked for help
|
||||
if (cmd.args.includes('--help')) {
|
||||
await run('docker', ['image', 'build', '--help']);
|
||||
return;
|
||||
}
|
||||
|
||||
const pkgPath = paths.resolveTarget(PKG_PATH);
|
||||
const pkg = await fs.readJson(pkgPath);
|
||||
const appConfigs = await findAppConfigs();
|
||||
const tempDistWorkspace = await createDistWorkspace([pkg.name], {
|
||||
buildDependencies: Boolean(cmd.build),
|
||||
files: [
|
||||
'package.json',
|
||||
'yarn.lock',
|
||||
'app-config.yaml',
|
||||
...appConfigs,
|
||||
{ src: paths.resolveTarget('Dockerfile'), dest: 'Dockerfile' },
|
||||
],
|
||||
skeleton: 'skeleton.tar',
|
||||
});
|
||||
console.log(`Dist workspace ready at ${tempDistWorkspace}`);
|
||||
|
||||
await run('docker', ['build', '.', '-t', imageTag], {
|
||||
// all args are forwarded to docker build
|
||||
await run('docker', ['image', 'build', '.', ...cmd.args], {
|
||||
cwd: tempDistWorkspace,
|
||||
});
|
||||
|
||||
await fs.remove(tempDistWorkspace);
|
||||
};
|
||||
|
||||
/**
|
||||
* Find all config files to copy into the image
|
||||
*/
|
||||
async function findAppConfigs(): Promise<string[]> {
|
||||
const files = [];
|
||||
|
||||
for (const name of await fs.readdir(paths.targetRoot)) {
|
||||
if (name.startsWith('app-config.') && name.endsWith('.yaml')) {
|
||||
files.push(name);
|
||||
}
|
||||
}
|
||||
|
||||
if (paths.targetRoot !== paths.targetDir) {
|
||||
const dirPath = relativePath(paths.targetRoot, paths.targetDir);
|
||||
|
||||
for (const name of await fs.readdir(paths.targetDir)) {
|
||||
if (name.startsWith('app-config.') && name.endsWith('.yaml')) {
|
||||
files.push(joinPath(dirPath, name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
@@ -19,5 +19,6 @@ import { paths } from '../../lib/paths';
|
||||
|
||||
export default async function clean() {
|
||||
await fs.remove(paths.resolveTarget('dist'));
|
||||
await fs.remove(paths.resolveTarget('dist-types'));
|
||||
await fs.remove(paths.resolveTarget('coverage'));
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ describe('createPlugin', () => {
|
||||
await createTemporaryPluginFolder(tempDir);
|
||||
await movePlugin(tempDir, pluginDir, id);
|
||||
await expect(fs.pathExists(pluginDir)).resolves.toBe(true);
|
||||
expect(pluginDir).toMatch(`/plugins\/${id}`);
|
||||
expect(pluginDir).toMatch(path.join('', 'plugins', id));
|
||||
} finally {
|
||||
await del(tempDir, { force: true });
|
||||
await del(rootDir, { force: true });
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* 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 { CommanderStatic } from 'commander';
|
||||
import { exitWithError } from '../lib/errors';
|
||||
|
||||
export function registerCommands(program: CommanderStatic) {
|
||||
program
|
||||
.command('app:build')
|
||||
.description('Build an app for a production release')
|
||||
.option('--stats', 'Write bundle stats to output directory')
|
||||
.action(lazy(() => import('./app/build').then(m => m.default)));
|
||||
|
||||
program
|
||||
.command('app:serve')
|
||||
.description('Serve an app for local development')
|
||||
.option('--check', 'Enable type checking and linting')
|
||||
.action(lazy(() => import('./app/serve').then(m => m.default)));
|
||||
|
||||
program
|
||||
.command('backend:build')
|
||||
.description('Build a backend plugin')
|
||||
.action(lazy(() => import('./backend/build').then(m => m.default)));
|
||||
|
||||
program
|
||||
.command('backend:build-image')
|
||||
.allowUnknownOption(true)
|
||||
.helpOption(', --backstage-cli-help') // Let docker handle --help
|
||||
.option('--build', 'Build packages before packing them into the image')
|
||||
.description(
|
||||
'Bundles the package into a docker image. All extra args are forwarded to docker image build',
|
||||
)
|
||||
.action(lazy(() => import('./backend/buildImage').then(m => m.default)));
|
||||
|
||||
program
|
||||
.command('backend:dev')
|
||||
.description('Start local development server with HMR for the backend')
|
||||
.option('--check', 'Enable type checking and linting')
|
||||
.option('--inspect', 'Enable debugger')
|
||||
.action(lazy(() => import('./backend/dev').then(m => m.default)));
|
||||
|
||||
program
|
||||
.command('app:diff')
|
||||
.option('--check', 'Fail if changes are required')
|
||||
.option('--yes', 'Apply all changes')
|
||||
.description('Diff an existing app with the creation template')
|
||||
.action(lazy(() => import('./app/diff').then(m => m.default)));
|
||||
|
||||
program
|
||||
.command('create-plugin')
|
||||
.description('Creates a new plugin in the current repository')
|
||||
.action(
|
||||
lazy(() => import('./create-plugin/createPlugin').then(m => m.default)),
|
||||
);
|
||||
|
||||
program
|
||||
.command('remove-plugin')
|
||||
.description('Removes plugin in the current repository')
|
||||
.action(
|
||||
lazy(() => import('./remove-plugin/removePlugin').then(m => m.default)),
|
||||
);
|
||||
|
||||
program
|
||||
.command('plugin:build')
|
||||
.description('Build a plugin')
|
||||
.action(lazy(() => import('./plugin/build').then(m => m.default)));
|
||||
|
||||
program
|
||||
.command('plugin:serve')
|
||||
.description('Serves the dev/ folder of a plugin')
|
||||
.option('--check', 'Enable type checking and linting')
|
||||
.action(lazy(() => import('./plugin/serve').then(m => m.default)));
|
||||
|
||||
program
|
||||
.command('plugin:export')
|
||||
.description('Exports the dev/ folder of a plugin')
|
||||
.option('--stats', 'Write bundle stats to output directory')
|
||||
.action(lazy(() => import('./plugin/export').then(m => m.default)));
|
||||
|
||||
program
|
||||
.command('plugin:diff')
|
||||
.option('--check', 'Fail if changes are required')
|
||||
.option('--yes', 'Apply all changes')
|
||||
.description('Diff an existing plugin with the creation template')
|
||||
.action(lazy(() => import('./plugin/diff').then(m => m.default)));
|
||||
|
||||
program
|
||||
.command('build')
|
||||
.description('Build a package for publishing')
|
||||
.option('--outputs <formats>', 'List of formats to output [types,cjs,esm]')
|
||||
.action(lazy(() => import('./build').then(m => m.default)));
|
||||
|
||||
program
|
||||
.command('lint')
|
||||
.option(
|
||||
'--format <format>',
|
||||
'Lint report output format',
|
||||
'eslint-formatter-friendly',
|
||||
)
|
||||
.option('--fix', 'Attempt to automatically fix violations')
|
||||
.description('Lint a package')
|
||||
.action(lazy(() => import('./lint').then(m => m.default)));
|
||||
|
||||
program
|
||||
.command('test')
|
||||
.allowUnknownOption(true) // Allows the command to run, but we still need to parse raw args
|
||||
.helpOption(', --backstage-cli-help') // Let Jest handle help
|
||||
.description('Run tests, forwarding args to Jest, defaulting to watch mode')
|
||||
.action(lazy(() => import('./testCommand').then(m => m.default)));
|
||||
|
||||
program
|
||||
.command('config:print')
|
||||
.option('--with-secrets', 'Include secrets in the printed configuration')
|
||||
.option(
|
||||
'--env <env>',
|
||||
'The environment to print configuration for [NODE_ENV or development]',
|
||||
)
|
||||
.option(
|
||||
'--format <format>',
|
||||
'Format to print the configuration in, either json or yaml [yaml]',
|
||||
)
|
||||
.description('Print the app configuration for the current package')
|
||||
.action(lazy(() => import('./config/print').then(m => m.default)));
|
||||
|
||||
program
|
||||
.command('prepack')
|
||||
.description('Prepares a package for packaging before publishing')
|
||||
.action(lazy(() => import('./pack').then(m => m.pre)));
|
||||
|
||||
program
|
||||
.command('postpack')
|
||||
.description('Restores the changes made by the prepack command')
|
||||
.action(lazy(() => import('./pack').then(m => m.post)));
|
||||
|
||||
program
|
||||
.command('clean')
|
||||
.description('Delete cache directories')
|
||||
.action(lazy(() => import('./clean/clean').then(m => m.default)));
|
||||
|
||||
program
|
||||
.command('build-workspace <workspace-dir> ...<packages>')
|
||||
.description('Builds a temporary dist workspace from the provided packages')
|
||||
.action(lazy(() => import('./buildWorkspace').then(m => m.default)));
|
||||
}
|
||||
|
||||
// Wraps an action function so that it always exits and handles errors
|
||||
function lazy(
|
||||
getActionFunc: () => Promise<(...args: any[]) => Promise<void>>,
|
||||
): (...args: any[]) => Promise<never> {
|
||||
return async (...args: any[]) => {
|
||||
try {
|
||||
const actionFunc = await getActionFunc();
|
||||
await actionFunc(...args);
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
exitWithError(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -39,7 +39,7 @@ const testPluginPackage = `${BACKSTAGE}/plugin-${testPluginName}`;
|
||||
const tempDir = path.join(os.tmpdir(), 'remove-plugin-test');
|
||||
|
||||
const removeEmptyLines = (file: string): string =>
|
||||
file.split('\n').filter(Boolean).join('\n');
|
||||
file.split(/\r?\n/).filter(Boolean).join('\n');
|
||||
|
||||
const createTestPackageFile = async (
|
||||
testFilePath: string,
|
||||
@@ -66,7 +66,7 @@ const createTestPluginFile = async (
|
||||
fse.copyFileSync(pluginsFilePath, testFilePath);
|
||||
const pluginNameCapitalized = testPluginName
|
||||
.split('-')
|
||||
.map((name) => capitalize(name))
|
||||
.map(name => capitalize(name))
|
||||
.join('');
|
||||
const exportStatement = `export { default as ${pluginNameCapitalized}} from @backstage/plugin-${testPluginName}`;
|
||||
addExportStatement(testFilePath, exportStatement);
|
||||
@@ -100,7 +100,7 @@ describe('removePlugin', () => {
|
||||
const packageFileContent = removeEmptyLines(
|
||||
fse.readFileSync(packageFilePath, 'utf8'),
|
||||
);
|
||||
expect(testFileContent === packageFileContent).toBe(true);
|
||||
expect(testFileContent).toBe(packageFileContent);
|
||||
} finally {
|
||||
fse.removeSync(testFilePath);
|
||||
}
|
||||
@@ -117,7 +117,7 @@ describe('removePlugin', () => {
|
||||
const pluginsFileContent = removeEmptyLines(
|
||||
fse.readFileSync(pluginsFilePaths, 'utf8'),
|
||||
);
|
||||
expect(testFileContent === pluginsFileContent).toBe(true);
|
||||
expect(testFileContent).toBe(pluginsFileContent);
|
||||
} finally {
|
||||
fse.removeSync(testFilePath);
|
||||
}
|
||||
@@ -138,7 +138,7 @@ describe('removePlugin', () => {
|
||||
'test@gmail.com',
|
||||
]);
|
||||
await removePluginFromCodeOwners(testFilePath, testPluginName);
|
||||
expect(testFileContent === codeOwnersFileContent).toBeTruthy();
|
||||
expect(testFileContent).toBe(codeOwnersFileContent);
|
||||
} finally {
|
||||
if (fse.existsSync(testFilePath)) fse.removeSync(testFilePath);
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ const removeAllStatementsContainingID = async (file: string, ID: string) => {
|
||||
const originalContent = await fse.readFile(file, 'utf8');
|
||||
const contentAfterRemoval = originalContent
|
||||
.split('\n')
|
||||
.filter((statement) => !statement.includes(`${ID}`)) // get rid of lines with pluginName
|
||||
.filter(statement => !statement.includes(`${ID}`)) // get rid of lines with pluginName
|
||||
.join('\n');
|
||||
if (originalContent !== contentAfterRemoval) {
|
||||
await fse.writeFile(file, contentAfterRemoval, 'utf8');
|
||||
@@ -100,7 +100,7 @@ export const removeReferencesFromPluginsFile = async (
|
||||
) => {
|
||||
const pluginNameCapitalized = pluginName
|
||||
.split('-')
|
||||
.map((name) => capitalize(name))
|
||||
.map(name => capitalize(name))
|
||||
.join('');
|
||||
|
||||
await Task.forItem('removing', 'export references', async () => {
|
||||
|
||||
+2
-160
@@ -18,151 +18,12 @@ import program from 'commander';
|
||||
import chalk from 'chalk';
|
||||
import { exitWithError } from './lib/errors';
|
||||
import { version } from './lib/version';
|
||||
import { registerCommands } from './commands';
|
||||
|
||||
const main = (argv: string[]) => {
|
||||
program.name('backstage-cli').version(version);
|
||||
|
||||
program
|
||||
.command('app:build')
|
||||
.description('Build an app for a production release')
|
||||
.option('--stats', 'Write bundle stats to output directory')
|
||||
.action(lazyAction(() => import('./commands/app/build'), 'default'));
|
||||
|
||||
program
|
||||
.command('app:serve')
|
||||
.description('Serve an app for local development')
|
||||
.option('--check', 'Enable type checking and linting')
|
||||
.action(lazyAction(() => import('./commands/app/serve'), 'default'));
|
||||
|
||||
program
|
||||
.command('backend:build')
|
||||
.description('Build a backend plugin')
|
||||
.action(lazyAction(() => import('./commands/backend/build'), 'default'));
|
||||
|
||||
program
|
||||
.command('backend:build-image <image-tag>')
|
||||
.description(
|
||||
'Builds a docker image from the package, with all local deps included',
|
||||
)
|
||||
.action(
|
||||
lazyAction(() => import('./commands/backend/buildImage'), 'default'),
|
||||
);
|
||||
|
||||
program
|
||||
.command('backend:dev')
|
||||
.description('Start local development server with HMR for the backend')
|
||||
.option('--check', 'Enable type checking and linting')
|
||||
.option('--inspect', 'Enable debugger')
|
||||
.action(lazyAction(() => import('./commands/backend/dev'), 'default'));
|
||||
|
||||
program
|
||||
.command('app:diff')
|
||||
.option('--check', 'Fail if changes are required')
|
||||
.option('--yes', 'Apply all changes')
|
||||
.description('Diff an existing app with the creation template')
|
||||
.action(lazyAction(() => import('./commands/app/diff'), 'default'));
|
||||
|
||||
program
|
||||
.command('create-plugin')
|
||||
.description('Creates a new plugin in the current repository')
|
||||
.action(
|
||||
lazyAction(
|
||||
() => import('./commands/create-plugin/createPlugin'),
|
||||
'default',
|
||||
),
|
||||
);
|
||||
|
||||
program
|
||||
.command('remove-plugin')
|
||||
.description('Removes plugin in the current repository')
|
||||
.action(
|
||||
lazyAction(
|
||||
() => import('./commands/remove-plugin/removePlugin'),
|
||||
'default',
|
||||
),
|
||||
);
|
||||
|
||||
program
|
||||
.command('plugin:build')
|
||||
.description('Build a plugin')
|
||||
.action(lazyAction(() => import('./commands/plugin/build'), 'default'));
|
||||
|
||||
program
|
||||
.command('plugin:serve')
|
||||
.description('Serves the dev/ folder of a plugin')
|
||||
.option('--check', 'Enable type checking and linting')
|
||||
.action(lazyAction(() => import('./commands/plugin/serve'), 'default'));
|
||||
|
||||
program
|
||||
.command('plugin:export')
|
||||
.description('Exports the dev/ folder of a plugin')
|
||||
.option('--stats', 'Write bundle stats to output directory')
|
||||
.action(lazyAction(() => import('./commands/plugin/export'), 'default'));
|
||||
|
||||
program
|
||||
.command('plugin:diff')
|
||||
.option('--check', 'Fail if changes are required')
|
||||
.option('--yes', 'Apply all changes')
|
||||
.description('Diff an existing plugin with the creation template')
|
||||
.action(lazyAction(() => import('./commands/plugin/diff'), 'default'));
|
||||
|
||||
program
|
||||
.command('build')
|
||||
.description('Build a package for publishing')
|
||||
.option('--outputs <formats>', 'List of formats to output [types,cjs,esm]')
|
||||
.action(lazyAction(() => import('./commands/build'), 'default'));
|
||||
|
||||
program
|
||||
.command('lint')
|
||||
.option(
|
||||
'--format <format>',
|
||||
'Lint report output format',
|
||||
'eslint-formatter-friendly',
|
||||
)
|
||||
.option('--fix', 'Attempt to automatically fix violations')
|
||||
.description('Lint a package')
|
||||
.action(lazyAction(() => import('./commands/lint'), 'default'));
|
||||
|
||||
program
|
||||
.command('test')
|
||||
.allowUnknownOption(true) // Allows the command to run, but we still need to parse raw args
|
||||
.helpOption(', --backstage-cli-help') // Let Jest handle help
|
||||
.description('Run tests, forwarding args to Jest, defaulting to watch mode')
|
||||
.action(lazyAction(() => import('./commands/testCommand'), 'default'));
|
||||
|
||||
program
|
||||
.command('config:print')
|
||||
.option('--with-secrets', 'Include secrets in the printed configuration')
|
||||
.option(
|
||||
'--env <env>',
|
||||
'The environment to print configuration for [NODE_ENV or development]',
|
||||
)
|
||||
.option(
|
||||
'--format <format>',
|
||||
'Format to print the configuration in, either json or yaml [yaml]',
|
||||
)
|
||||
.description('Print the app configuration for the current package')
|
||||
.action(lazyAction(() => import('./commands/config/print'), 'default'));
|
||||
|
||||
program
|
||||
.command('prepack')
|
||||
.description('Prepares a package for packaging before publishing')
|
||||
.action(lazyAction(() => import('./commands/pack'), 'pre'));
|
||||
|
||||
program
|
||||
.command('postpack')
|
||||
.description('Restores the changes made by the prepack command')
|
||||
.action(lazyAction(() => import('./commands/pack'), 'post'));
|
||||
|
||||
program
|
||||
.command('clean')
|
||||
.description('Delete cache directories')
|
||||
.action(lazyAction(() => import('./commands/clean/clean'), 'default'));
|
||||
|
||||
program
|
||||
.command('build-workspace <workspace-dir> ...<packages>')
|
||||
.description('Builds a temporary dist workspace from the provided packages')
|
||||
.action(lazyAction(() => import('./commands/buildWorkspace'), 'default'));
|
||||
registerCommands(program);
|
||||
|
||||
program.on('command:*', () => {
|
||||
console.log();
|
||||
@@ -181,25 +42,6 @@ const main = (argv: string[]) => {
|
||||
program.parse(argv);
|
||||
};
|
||||
|
||||
// Wraps an action function so that it always exits and handles errors
|
||||
function lazyAction<T extends readonly any[], Export extends string>(
|
||||
actionRequireFunc: () => Promise<
|
||||
{ [name in Export]: (...args: T) => Promise<any> }
|
||||
>,
|
||||
exportName: Export,
|
||||
): (...args: T) => Promise<never> {
|
||||
return async (...args: T) => {
|
||||
try {
|
||||
const module = await actionRequireFunc();
|
||||
const actionFunc = module[exportName];
|
||||
await actionFunc(...args);
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
exitWithError(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
process.on('unhandledRejection', rejection => {
|
||||
if (rejection instanceof Error) {
|
||||
exitWithError(rejection);
|
||||
|
||||
@@ -37,7 +37,7 @@ export const makeConfigs = async (
|
||||
options: BuildOptions,
|
||||
): Promise<RollupOptions[]> => {
|
||||
const typesInput = paths.resolveTargetRoot(
|
||||
'dist',
|
||||
'dist-types',
|
||||
relativePath(paths.targetRoot, paths.targetDir),
|
||||
'src/index.d.ts',
|
||||
);
|
||||
|
||||
@@ -15,10 +15,14 @@
|
||||
*/
|
||||
|
||||
import fs from 'fs-extra';
|
||||
import { resolve as resolvePath, relative as relativePath } from 'path';
|
||||
import {
|
||||
join as joinPath,
|
||||
resolve as resolvePath,
|
||||
relative as relativePath,
|
||||
} from 'path';
|
||||
import { paths } from '../paths';
|
||||
import { run } from '../run';
|
||||
import tar from 'tar';
|
||||
import tar, { CreateOptions } from 'tar';
|
||||
import { tmpdir } from 'os';
|
||||
|
||||
type LernaPackage = {
|
||||
@@ -48,6 +52,17 @@ type Options = {
|
||||
* Defaults to ['yarn.lock', 'package.json'].
|
||||
*/
|
||||
files?: FileEntry[];
|
||||
|
||||
/**
|
||||
* If set to true, the target packages are built before they are packaged into the workspace.
|
||||
*/
|
||||
buildDependencies?: boolean;
|
||||
|
||||
/**
|
||||
* If set, creates a skeleton tarball that contains all package.json files
|
||||
* with the same structure as the workspace dir.
|
||||
*/
|
||||
skeleton?: 'skeleton.tar';
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -68,6 +83,13 @@ export async function createDistWorkspace(
|
||||
|
||||
const targets = await findTargetPackages(packageNames);
|
||||
|
||||
if (options.buildDependencies) {
|
||||
const scopeArgs = targets.flatMap(target => ['--scope', target.name]);
|
||||
await run('yarn', ['lerna', 'run', ...scopeArgs, 'build'], {
|
||||
cwd: paths.targetRoot,
|
||||
});
|
||||
}
|
||||
|
||||
await moveToDistWorkspace(targetDir, targets);
|
||||
|
||||
const files: FileEntry[] = options.files ?? ['yarn.lock', 'package.json'];
|
||||
@@ -77,6 +99,24 @@ export async function createDistWorkspace(
|
||||
const dest = typeof file === 'string' ? file : file.dest;
|
||||
await fs.copy(paths.resolveTargetRoot(src), resolvePath(targetDir, dest));
|
||||
}
|
||||
|
||||
if (options.skeleton) {
|
||||
const skeletonFiles = targets.map(target => {
|
||||
const dir = relativePath(paths.targetRoot, target.location);
|
||||
return joinPath(dir, 'package.json');
|
||||
});
|
||||
|
||||
await tar.create(
|
||||
{
|
||||
file: resolvePath(targetDir, options.skeleton),
|
||||
cwd: targetDir,
|
||||
portable: true,
|
||||
noMtime: true,
|
||||
} as CreateOptions & { noMtime: boolean },
|
||||
skeletonFiles,
|
||||
);
|
||||
}
|
||||
|
||||
return targetDir;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,8 +37,10 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^26.0.7",
|
||||
"@types/mock-fs": "^4.10.0",
|
||||
"@types/node": "^12.0.0",
|
||||
"@types/yup": "^0.28.2"
|
||||
"@types/yup": "^0.28.2",
|
||||
"mock-fs": "^4.13.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -14,46 +14,48 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const pathExists = jest.fn();
|
||||
|
||||
jest.mock('fs-extra', () => ({ pathExists }));
|
||||
|
||||
import mockFs from 'mock-fs';
|
||||
import { resolveStaticConfig } from './resolver';
|
||||
|
||||
function normalizePaths(paths: string[]) {
|
||||
return paths.map(p =>
|
||||
p
|
||||
.replace(/^[a-z]:/i, '')
|
||||
.split('\\')
|
||||
.join('/'),
|
||||
);
|
||||
}
|
||||
|
||||
describe('resolveStaticConfig', () => {
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
it('should resolve no files for empty roots', async () => {
|
||||
mockFs({});
|
||||
const resolved = await resolveStaticConfig({
|
||||
env: 'development',
|
||||
rootPaths: [],
|
||||
});
|
||||
|
||||
expect(resolved).toEqual([]);
|
||||
expect(pathExists).not.toHaveBeenCalled();
|
||||
expect(normalizePaths(resolved)).toEqual([]);
|
||||
});
|
||||
|
||||
it('should resolve a single app-config', async () => {
|
||||
pathExists.mockImplementation(async (path: string) =>
|
||||
['/repo/app-config.yaml'].includes(path),
|
||||
);
|
||||
mockFs({ '/repo/app-config.yaml': '' });
|
||||
const resolved = await resolveStaticConfig({
|
||||
env: 'development',
|
||||
rootPaths: ['/repo'],
|
||||
});
|
||||
|
||||
expect(resolved).toEqual(['/repo/app-config.yaml']);
|
||||
expect(pathExists).toHaveBeenCalledTimes(4);
|
||||
expect(normalizePaths(resolved)).toEqual(['/repo/app-config.yaml']);
|
||||
});
|
||||
|
||||
it('should resolve a app-configs in different directories', async () => {
|
||||
pathExists.mockImplementation(async (path: string) =>
|
||||
['/repo/app-config.yaml', '/repo/packages/a/app-config.yaml'].includes(
|
||||
path,
|
||||
),
|
||||
);
|
||||
mockFs({
|
||||
'/repo/app-config.yaml': '',
|
||||
'/repo/packages/a/app-config.yaml': '',
|
||||
});
|
||||
const resolved = await resolveStaticConfig({
|
||||
env: 'development',
|
||||
rootPaths: [
|
||||
@@ -64,53 +66,54 @@ describe('resolveStaticConfig', () => {
|
||||
],
|
||||
});
|
||||
|
||||
expect(resolved).toEqual([
|
||||
expect(normalizePaths(resolved)).toEqual([
|
||||
'/repo/app-config.yaml',
|
||||
'/repo/packages/a/app-config.yaml',
|
||||
]);
|
||||
expect(pathExists).toHaveBeenCalledTimes(16);
|
||||
});
|
||||
|
||||
it('should resolve env and local configs', async () => {
|
||||
pathExists.mockImplementation(async (path: string) =>
|
||||
[
|
||||
'/repo/app-config.yaml',
|
||||
'/repo/app-config.local.yaml',
|
||||
'/repo/app-config.production.yaml',
|
||||
'/repo/app-config.production.local.yaml',
|
||||
'/repo/app-config.development.local.yaml',
|
||||
'/repo/packages/a/app-config.development.yaml',
|
||||
'/repo/packages/a/app-config.local.yaml',
|
||||
].includes(path),
|
||||
);
|
||||
mockFs({
|
||||
'/repo/app-config.yaml': '',
|
||||
'/repo/app-config.local.yaml': '',
|
||||
'/repo/app-config.production.yaml': '',
|
||||
'/repo/app-config.production.local.yaml': '',
|
||||
'/repo/app-config.development.local.yaml': '',
|
||||
'/repo/packages/a/app-config.development.yaml': '',
|
||||
'/repo/packages/a/app-config.local.yaml': '',
|
||||
});
|
||||
const resolved = await resolveStaticConfig({
|
||||
env: 'development',
|
||||
rootPaths: ['/repo', '/repo/packages/a'],
|
||||
});
|
||||
|
||||
expect(resolved).toEqual([
|
||||
expect(normalizePaths(resolved)).toEqual([
|
||||
'/repo/app-config.yaml',
|
||||
'/repo/app-config.local.yaml',
|
||||
'/repo/app-config.development.local.yaml',
|
||||
'/repo/packages/a/app-config.local.yaml',
|
||||
'/repo/packages/a/app-config.development.yaml',
|
||||
]);
|
||||
expect(pathExists).toHaveBeenCalledTimes(8);
|
||||
});
|
||||
|
||||
it('resolves suffixed configs in the correct order', async () => {
|
||||
pathExists.mockImplementation(async () => true);
|
||||
mockFs({
|
||||
'/repo/app-config.yaml': '',
|
||||
'/repo/app-config.local.yaml': '',
|
||||
'/repo/app-config.production.yaml': '',
|
||||
'/repo/app-config.production.local.yaml': '',
|
||||
});
|
||||
|
||||
const resolved = await resolveStaticConfig({
|
||||
env: 'production',
|
||||
rootPaths: ['/repo'],
|
||||
});
|
||||
|
||||
expect(resolved).toEqual([
|
||||
expect(normalizePaths(resolved)).toEqual([
|
||||
'/repo/app-config.yaml',
|
||||
'/repo/app-config.local.yaml',
|
||||
'/repo/app-config.production.yaml',
|
||||
'/repo/app-config.production.local.yaml',
|
||||
]);
|
||||
expect(pathExists).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,45 +15,30 @@
|
||||
*/
|
||||
|
||||
import { loadConfig } from './loader';
|
||||
|
||||
jest.mock('fs-extra', () => {
|
||||
const mockFiles: { [path in string]: string } = {
|
||||
'/root/app-config.yaml': `
|
||||
app:
|
||||
title: Example App
|
||||
sessionKey:
|
||||
$secret:
|
||||
file: secrets/session-key.txt
|
||||
`,
|
||||
'/root/app-config.development.yaml': `
|
||||
app:
|
||||
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 {
|
||||
async readFile(path: string) {
|
||||
if (path in mockFiles) {
|
||||
return mockFiles[path];
|
||||
}
|
||||
throw new Error(`File not found, ${path}`);
|
||||
},
|
||||
async pathExists(path: string) {
|
||||
return path in mockFiles;
|
||||
},
|
||||
};
|
||||
});
|
||||
import mockFs from 'mock-fs';
|
||||
|
||||
describe('loadConfig', () => {
|
||||
beforeAll(() => {
|
||||
mockFs({
|
||||
'/root/app-config.yaml': `
|
||||
app:
|
||||
title: Example App
|
||||
sessionKey:
|
||||
$secret:
|
||||
file: secrets/session-key.txt
|
||||
`,
|
||||
'/root/app-config.development.yaml': `
|
||||
app:
|
||||
sessionKey: development-key
|
||||
`,
|
||||
'/root/secrets/session-key.txt': 'abc123',
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
it('loads config without secrets', async () => {
|
||||
await expect(
|
||||
loadConfig({
|
||||
|
||||
@@ -23,7 +23,7 @@ const PREFIX = 'okta.';
|
||||
describe('OktaAuth', () => {
|
||||
it('should get refreshed access token', async () => {
|
||||
const getSession = jest.fn().mockResolvedValue({
|
||||
providerInfo: { accessToken: 'access-token', expiresAt: theFuture },
|
||||
providerInfo: { accessToken: 'access-token', expiresAt: theFuture },
|
||||
});
|
||||
const oktaAuth = new OktaAuth({ getSession } as any);
|
||||
|
||||
@@ -116,7 +116,10 @@ describe('OktaAuth', () => {
|
||||
['profile email', ['profile', 'email']],
|
||||
[`${PREFIX}groups.manage`, [`${PREFIX}groups.manage`]],
|
||||
['groups.read', [`${PREFIX}groups.read`]],
|
||||
[`${PREFIX}groups.manage groups.read, openid`, [`${PREFIX}groups.manage`, `${PREFIX}groups.read`, 'openid']],
|
||||
[
|
||||
`${PREFIX}groups.manage groups.read, openid`,
|
||||
[`${PREFIX}groups.manage`, `${PREFIX}groups.read`, 'openid'],
|
||||
],
|
||||
[`email\t ${PREFIX}groups.read`, ['email', `${PREFIX}groups.read`]],
|
||||
|
||||
// Some incorrect scopes that we don't try to fix
|
||||
@@ -126,4 +129,4 @@ describe('OktaAuth', () => {
|
||||
])(`should normalize scopes correctly - %p`, (scope, scopes) => {
|
||||
expect(OktaAuth.normalizeScopes(scope)).toEqual(new Set(scopes));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,4 +15,4 @@
|
||||
*/
|
||||
|
||||
export * from './types';
|
||||
export { default as OktaAuth } from './OktaAuth';
|
||||
export { default as OktaAuth } from './OktaAuth';
|
||||
|
||||
@@ -15,21 +15,21 @@
|
||||
*/
|
||||
|
||||
import { defaultConfigLoader } from './createApp';
|
||||
import { AppConfig } from '@backstage/config';
|
||||
|
||||
(process as any).env = { NODE_ENV: 'test' };
|
||||
const anyEnv = process.env as any;
|
||||
|
||||
describe('defaultConfigLoader', () => {
|
||||
afterEach(() => {
|
||||
delete process.env.APP_CONFIG;
|
||||
delete anyEnv.APP_CONFIG;
|
||||
});
|
||||
|
||||
it('loads static config', async () => {
|
||||
Object.defineProperty(process.env, 'APP_CONFIG', {
|
||||
configurable: true,
|
||||
value: [
|
||||
{ data: { my: 'config' }, context: 'a' },
|
||||
{ data: { my: 'override-config' }, context: 'b' },
|
||||
] as AppConfig[],
|
||||
});
|
||||
anyEnv.APP_CONFIG = [
|
||||
{ data: { my: 'config' }, context: 'a' },
|
||||
{ data: { my: 'override-config' }, context: 'b' },
|
||||
];
|
||||
|
||||
const configs = await defaultConfigLoader();
|
||||
expect(configs).toEqual([
|
||||
{ data: { my: 'config' }, context: 'a' },
|
||||
@@ -38,13 +38,11 @@ describe('defaultConfigLoader', () => {
|
||||
});
|
||||
|
||||
it('loads runtime config', async () => {
|
||||
Object.defineProperty(process.env, 'APP_CONFIG', {
|
||||
configurable: true,
|
||||
value: [
|
||||
{ data: { my: 'override-config' }, context: 'a' },
|
||||
{ data: { my: 'config' }, context: 'b' },
|
||||
] as AppConfig[],
|
||||
});
|
||||
anyEnv.APP_CONFIG = [
|
||||
{ data: { my: 'override-config' }, context: 'a' },
|
||||
{ data: { my: 'config' }, context: 'b' },
|
||||
];
|
||||
|
||||
const configs = await (defaultConfigLoader as any)(
|
||||
'{"my":"runtime-config"}',
|
||||
);
|
||||
@@ -62,20 +60,14 @@ describe('defaultConfigLoader', () => {
|
||||
});
|
||||
|
||||
it('fails to load invalid static config', async () => {
|
||||
Object.defineProperty(process.env, 'APP_CONFIG', {
|
||||
configurable: true,
|
||||
value: { my: 'invalid-config' } as any,
|
||||
});
|
||||
anyEnv.APP_CONFIG = { my: 'invalid-config' };
|
||||
await expect(defaultConfigLoader()).rejects.toThrow(
|
||||
'Static configuration has invalid format',
|
||||
);
|
||||
});
|
||||
|
||||
it('fails to load bad runtime config', async () => {
|
||||
Object.defineProperty(process.env, 'APP_CONFIG', {
|
||||
configurable: true,
|
||||
value: [{ data: { my: 'config' }, context: 'a' }] as AppConfig[],
|
||||
});
|
||||
anyEnv.APP_CONFIG = [{ data: { my: 'config' }, context: 'a' }];
|
||||
|
||||
await expect((defaultConfigLoader as any)('}')).rejects.toThrow(
|
||||
'Failed to load runtime configuration, SyntaxError: Unexpected token } in JSON at position 0',
|
||||
|
||||
@@ -51,7 +51,7 @@ export const ConditionalButtons = () => {
|
||||
<TextField
|
||||
variant="outlined"
|
||||
placeholder="Required*"
|
||||
onChange={(e) => setRequired(!!e.target.value)}
|
||||
onChange={e => setRequired(!!e.target.value)}
|
||||
/>
|
||||
</SimpleStepperStep>
|
||||
<SimpleStepperStep title="Step 2">
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
import { Chip } from '@material-ui/core';
|
||||
|
||||
export default {
|
||||
title: 'Chip',
|
||||
component: Chip,
|
||||
};
|
||||
|
||||
export const Default = () => <Chip label="Default" />;
|
||||
|
||||
export const LargeDeletable = () => (
|
||||
<Chip label="Large deletable" size="medium" onDelete={() => ({})} />
|
||||
);
|
||||
|
||||
export const LargeNotDeletable = () => (
|
||||
<Chip label="Large not deletable" size="medium" />
|
||||
);
|
||||
|
||||
export const SmallDeletable = () => (
|
||||
<Chip label="Small deletable" size="small" onDelete={() => ({})} />
|
||||
);
|
||||
|
||||
export const SmallNotDeletable = () => (
|
||||
<Chip label="Small not deletable" size="small" />
|
||||
);
|
||||
@@ -15,9 +15,8 @@
|
||||
*/
|
||||
import React, { FC } from 'react';
|
||||
import { InfoCard } from '.';
|
||||
import { Grid } from '@material-ui/core';
|
||||
import { Grid, Typography } from '@material-ui/core';
|
||||
|
||||
const cardContentStyle = { height: 200, width: 500 };
|
||||
const linkInfo = { title: 'Go to XYZ Location', link: '#' };
|
||||
|
||||
export default {
|
||||
@@ -25,24 +24,35 @@ export default {
|
||||
component: InfoCard,
|
||||
};
|
||||
|
||||
const text = (
|
||||
<Typography paragraph>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
|
||||
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
|
||||
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
||||
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
|
||||
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat
|
||||
non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||
</Typography>
|
||||
);
|
||||
|
||||
const Wrapper: FC<{}> = ({ children }) => (
|
||||
<Grid container spacing={4}>
|
||||
<Grid item>{children}</Grid>
|
||||
<Grid item xs={4}>
|
||||
{children}
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
|
||||
export const Default = () => (
|
||||
<Wrapper>
|
||||
<InfoCard title="Information Card">
|
||||
<div style={cardContentStyle} />
|
||||
</InfoCard>
|
||||
<InfoCard title="Information Card">{text}</InfoCard>
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
export const Subhead = () => (
|
||||
<Wrapper>
|
||||
<InfoCard title="Information Card" subheader="Subhead">
|
||||
<div style={cardContentStyle} />
|
||||
<InfoCard title="Information Card" subheader="Subheader">
|
||||
{text}
|
||||
</InfoCard>
|
||||
</Wrapper>
|
||||
);
|
||||
@@ -50,7 +60,7 @@ export const Subhead = () => (
|
||||
export const LinkInFooter = () => (
|
||||
<Wrapper>
|
||||
<InfoCard title="Information Card" deepLink={linkInfo}>
|
||||
<div style={cardContentStyle} />
|
||||
{text}
|
||||
</InfoCard>
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
@@ -97,7 +97,7 @@ const columns: TableColumn[] = [
|
||||
const tabs = [
|
||||
{ label: 'Overview' },
|
||||
{ label: 'CI/CD' },
|
||||
{ label: 'Cost Efficency' },
|
||||
{ label: 'Cost Efficiency' },
|
||||
{ label: 'Code Coverage' },
|
||||
{ label: 'Test' },
|
||||
{ label: 'Compliance Advisor' },
|
||||
@@ -137,38 +137,38 @@ const DataGrid = () => (
|
||||
</Grid>
|
||||
<Grid item xs>
|
||||
<InfoCard
|
||||
title="Information Card"
|
||||
deepLink={{ title: 'LEARN MORE ABOUT RIGHTSIZING FOR GKE', link: '' }}
|
||||
title="Additional Information"
|
||||
deepLink={{ title: 'Learn more about GKE', link: '' }}
|
||||
>
|
||||
<b>Rightsize GKE deployment</b>
|
||||
<p>
|
||||
<Typography variant="h6">Rightsize GKE deployment</Typography>
|
||||
<Typography paragraph>
|
||||
Services are considered underutilized in GKE when the average usage of
|
||||
requested cores is less than 80%.
|
||||
</p>
|
||||
<b>What can I do?</b>
|
||||
<p>
|
||||
</Typography>
|
||||
<Typography variant="h6">What can I do?</Typography>
|
||||
<Typography paragraph>
|
||||
Review requested core and limit settings. Check HPA target scaling
|
||||
settings in hpa.yaml. The recommended value for
|
||||
targetCPUUtilizationPercentage is 80.
|
||||
</p>
|
||||
<p>
|
||||
settings in <code>hpa.yaml</code>. The recommended value for
|
||||
<code>targetCPUUtilizationPercentage</code> is <code>80</code>.
|
||||
</Typography>
|
||||
<Typography paragraph>
|
||||
For single pods, there is of course no HPA. But it can also be useful
|
||||
to think about a single pod out of a larger deployment, then modify
|
||||
based on HPA requirements. Within a pod, each container has its own
|
||||
CPU and memory requests and limits.
|
||||
</p>
|
||||
<b>Definitions</b>
|
||||
<p>
|
||||
</Typography>
|
||||
<Typography variant="h6">Definitions</Typography>
|
||||
<Typography paragraph>
|
||||
A request is a minimum reserved value; a container will never have
|
||||
less than this amount allocated to it, even if it doesn't actually use
|
||||
it. Requests are used for determining what nodes to schedule pods on
|
||||
(bin-packing). The tension here is between not allocating resources we
|
||||
don't need, and having easy-enough access to enough resources to be
|
||||
able to function.
|
||||
</p>
|
||||
<b>
|
||||
</Typography>
|
||||
<Typography paragraph>
|
||||
Contact <Link>#cost-awareness</Link> for information and support.
|
||||
</b>
|
||||
</Typography>
|
||||
</InfoCard>
|
||||
</Grid>
|
||||
</Grid>
|
||||
@@ -195,37 +195,41 @@ const ExampleContentHeader = ({ selectedTab }: { selectedTab?: number }) => (
|
||||
export const PluginWithData = () => {
|
||||
const [selectedTab, setSelectedTab] = useState<number>(2);
|
||||
return (
|
||||
<Page theme={pageTheme.tool}>
|
||||
<ExampleHeader />
|
||||
<HeaderTabs
|
||||
selectedIndex={selectedTab}
|
||||
onChange={index => setSelectedTab(index)}
|
||||
tabs={tabs.map(({ label }, index) => ({
|
||||
id: index.toString(),
|
||||
label,
|
||||
}))}
|
||||
/>
|
||||
<Content>
|
||||
<ExampleContentHeader selectedTab={selectedTab} />
|
||||
<DataGrid />
|
||||
</Content>
|
||||
</Page>
|
||||
<div style={{ border: '1px solid #ddd' }}>
|
||||
<Page theme={pageTheme.tool}>
|
||||
<ExampleHeader />
|
||||
<HeaderTabs
|
||||
selectedIndex={selectedTab}
|
||||
onChange={index => setSelectedTab(index)}
|
||||
tabs={tabs.map(({ label }, index) => ({
|
||||
id: index.toString(),
|
||||
label,
|
||||
}))}
|
||||
/>
|
||||
<Content>
|
||||
<ExampleContentHeader selectedTab={selectedTab} />
|
||||
<DataGrid />
|
||||
</Content>
|
||||
</Page>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const PluginWithTable = () => {
|
||||
return (
|
||||
<Page theme={pageTheme.tool}>
|
||||
<ExampleHeader />
|
||||
<Content>
|
||||
<ExampleContentHeader />
|
||||
<Table
|
||||
options={{ paging: true, padding: 'dense' }}
|
||||
data={generateTestData(10)}
|
||||
columns={columns}
|
||||
title="Example Content"
|
||||
/>
|
||||
</Content>
|
||||
</Page>
|
||||
<div style={{ border: '1px solid #ddd' }}>
|
||||
<Page theme={pageTheme.tool}>
|
||||
<ExampleHeader />
|
||||
<Content>
|
||||
<ExampleContentHeader />
|
||||
<Table
|
||||
options={{ paging: true, padding: 'dense' }}
|
||||
data={generateTestData(10)}
|
||||
columns={columns}
|
||||
title="Example Content"
|
||||
/>
|
||||
</Content>
|
||||
</Page>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -29,9 +29,7 @@ import HomeOutlinedIcon from '@material-ui/icons/HomeOutlined';
|
||||
import AddCircleOutlineIcon from '@material-ui/icons/AddCircleOutline';
|
||||
import Star from '@material-ui/icons/Star';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import {
|
||||
githubAuthApiRef,
|
||||
} from '@backstage/core-api';
|
||||
import { githubAuthApiRef } from '@backstage/core-api';
|
||||
|
||||
export default {
|
||||
title: 'Sidebar',
|
||||
@@ -60,12 +58,14 @@ export const SampleSidebar = () => (
|
||||
<SidebarIntro />
|
||||
<SidebarSpace />
|
||||
<SidebarDivider />
|
||||
<SidebarUserSettings providerSettings={
|
||||
<OAuthProviderSettings
|
||||
title="Github"
|
||||
apiRef={githubAuthApiRef}
|
||||
icon={Star}
|
||||
/>
|
||||
} />
|
||||
<SidebarUserSettings
|
||||
providerSettings={
|
||||
<OAuthProviderSettings
|
||||
title="Github"
|
||||
apiRef={githubAuthApiRef}
|
||||
icon={Star}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Sidebar>
|
||||
);
|
||||
|
||||
@@ -22,6 +22,7 @@ node_modules/
|
||||
|
||||
# Build output
|
||||
dist
|
||||
dist-types
|
||||
|
||||
# Temporary change files created by Vim
|
||||
*.swp
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
app:
|
||||
baseUrl: http://localhost:3000
|
||||
|
||||
backend:
|
||||
baseUrl: http://localhost:7000
|
||||
listen:
|
||||
port: 7000
|
||||
cors:
|
||||
origin: http://localhost:3000
|
||||
methods: [GET, POST, PUT, DELETE]
|
||||
credentials: true
|
||||
@@ -1,6 +1,6 @@
|
||||
app:
|
||||
title: Scaffolded Backstage App
|
||||
baseUrl: http://localhost:3000
|
||||
baseUrl: http://localhost:7000
|
||||
|
||||
organization:
|
||||
name: Acme Corporation
|
||||
@@ -9,10 +9,6 @@ backend:
|
||||
baseUrl: http://localhost:7000
|
||||
listen:
|
||||
port: 7000
|
||||
cors:
|
||||
origin: http://localhost:3000
|
||||
methods: [GET, POST, PUT, DELETE]
|
||||
credentials: true
|
||||
{{#if dbTypeSqlite}}
|
||||
database:
|
||||
client: sqlite3
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"start": "yarn workspace app start",
|
||||
"build": "lerna run build",
|
||||
"tsc": "tsc",
|
||||
"tsc:full": "tsc --skipLibCheck false --incremental false",
|
||||
"clean": "backstage-cli clean && lerna run clean",
|
||||
"diff": "lerna run diff --",
|
||||
"test": "lerna run test --since origin/master -- --coverage",
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@backstage/cli": "^{{version}}",
|
||||
"@backstage/core": "^{{version}}",
|
||||
"@backstage/plugin-api-docs": "^{{version}}",
|
||||
"@backstage/plugin-catalog": "^{{version}}",
|
||||
"@backstage/plugin-register-component": "^{{version}}",
|
||||
"@backstage/plugin-scaffolder": "^{{version}}",
|
||||
|
||||
@@ -10,6 +10,7 @@ import * as plugins from './plugins';
|
||||
import { AppSidebar } from './sidebar';
|
||||
import { Route, Routes, Navigate } from 'react-router';
|
||||
import { Router as CatalogRouter } from '@backstage/plugin-catalog';
|
||||
import { Router as DocsRouter } from '@backstage/plugin-techdocs';
|
||||
import { EntityPage } from './components/catalog/EntityPage';
|
||||
|
||||
const app = createApp({
|
||||
@@ -33,6 +34,7 @@ const App: FC<{}> = () => (
|
||||
path="/catalog/*"
|
||||
element={<CatalogRouter EntityPage={EntityPage} />}
|
||||
/>
|
||||
<Route path="/docs/*" element={<DocsRouter />} />
|
||||
<Navigate key="/" to="/catalog" />
|
||||
{deprecatedAppRoutes}
|
||||
</Routes>
|
||||
|
||||
+23
@@ -21,6 +21,9 @@ import {
|
||||
Router as CircleCIRouter,
|
||||
isPluginApplicableToEntity as isCircleCIAvailable,
|
||||
} from '@backstage/plugin-circleci';
|
||||
import { Router as ApiDocsRouter } from '@backstage/plugin-api-docs';
|
||||
import { EmbeddedDocsRouter as DocsRouter } from '@backstage/plugin-techdocs';
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
EntityPageLayout,
|
||||
@@ -69,6 +72,16 @@ const ServiceEntityPage = ({ entity }: { entity: Entity }) => (
|
||||
title="CI/CD"
|
||||
element={<CICDSwitcher entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/api/*"
|
||||
title="API"
|
||||
element={<ApiDocsRouter entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/docs/*"
|
||||
title="Docs"
|
||||
element={<DocsRouter entity={entity} />}
|
||||
/>
|
||||
</EntityPageLayout>
|
||||
);
|
||||
|
||||
@@ -84,6 +97,11 @@ const WebsiteEntityPage = ({ entity }: { entity: Entity }) => (
|
||||
title="CI/CD"
|
||||
element={<CICDSwitcher entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/docs/*"
|
||||
title="Docs"
|
||||
element={<DocsRouter entity={entity} />}
|
||||
/>
|
||||
</EntityPageLayout>
|
||||
);
|
||||
|
||||
@@ -94,6 +112,11 @@ const DefaultEntityPage = ({ entity }: { entity: Entity }) => (
|
||||
title="Overview"
|
||||
element={<OverviewContent entity={entity} />}
|
||||
/>
|
||||
<EntityPageLayout.Content
|
||||
path="/docs/*"
|
||||
title="Docs"
|
||||
element={<DocsRouter entity={entity} />}
|
||||
/>
|
||||
</EntityPageLayout>
|
||||
);
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"build": "backstage-cli backend:build",
|
||||
"build-image": "backstage-cli backend:build-image example-backend",
|
||||
"build-image": "backstage-cli backend:build-image --build --tag example-backend",
|
||||
"start": "backstage-cli backend:dev",
|
||||
"lint": "backstage-cli lint",
|
||||
"test": "backstage-cli test",
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
],
|
||||
"exclude": ["node_modules"],
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": ".",
|
||||
"skipLibCheck": true
|
||||
"outDir": "dist-types",
|
||||
"rootDir": "."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,4 +22,24 @@ addParameters({
|
||||
// Set the initial theme
|
||||
current: 'light',
|
||||
},
|
||||
layout: 'fullscreen',
|
||||
});
|
||||
|
||||
export const parameters = {
|
||||
options: {
|
||||
storySort: {
|
||||
order: [
|
||||
'Example Plugin',
|
||||
'Header',
|
||||
'Sidebar',
|
||||
'Tabs',
|
||||
'Information Card',
|
||||
'Tabbed Card',
|
||||
'Table',
|
||||
'Status',
|
||||
'Trendline',
|
||||
'Progress Card',
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -17,11 +17,11 @@
|
||||
"@backstage/theme": "^0.1.1-alpha.21"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@storybook/addon-actions": "^5.3.17",
|
||||
"@storybook/addon-links": "^5.3.17",
|
||||
"@storybook/addon-storysource": "^5.3.18",
|
||||
"@storybook/addons": "^6.0.4",
|
||||
"@storybook/react": "^5.3.17",
|
||||
"storybook-dark-mode": "^0.6.1"
|
||||
"@storybook/addon-actions": "^6.0.21",
|
||||
"@storybook/addon-links": "^6.0.21",
|
||||
"@storybook/addon-storysource": "^6.0.21",
|
||||
"@storybook/addons": "^6.0.21",
|
||||
"@storybook/react": "^6.0.21",
|
||||
"storybook-dark-mode": "^1.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
site_name: 'mock-docs'
|
||||
site_description: 'mock-docs site description'
|
||||
|
||||
nav:
|
||||
nav:
|
||||
- Home: index.md
|
||||
- SubDocs: '!include ./sub-docs/mkdocs.yml'
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
site_name: subdocs
|
||||
|
||||
nav:
|
||||
- Home 2: "index.md"
|
||||
- Home 2: 'index.md'
|
||||
|
||||
@@ -192,10 +192,31 @@ export function createThemeOverrides(theme: BackstageTheme): Overrides {
|
||||
},
|
||||
MuiChip: {
|
||||
root: {
|
||||
backgroundColor: '#D9D9D9',
|
||||
// By default there's no margin, but it's usually wanted, so we add some trailing margin
|
||||
marginRight: theme.spacing(1),
|
||||
marginBottom: theme.spacing(1),
|
||||
},
|
||||
label: {
|
||||
color: theme.palette.grey[900],
|
||||
lineHeight: `${theme.spacing(2.5)}px`,
|
||||
fontWeight: theme.typography.fontWeightMedium,
|
||||
fontSize: `${theme.spacing(1.75)}px`,
|
||||
},
|
||||
labelSmall: {
|
||||
fontSize: `${theme.spacing(1.5)}px`,
|
||||
},
|
||||
deleteIcon: {
|
||||
color: theme.palette.grey[500],
|
||||
width: `${theme.spacing(3)}px`,
|
||||
height: `${theme.spacing(3)}px`,
|
||||
margin: `0 ${theme.spacing(0.75)}px 0 -${theme.spacing(0.75)}px`,
|
||||
},
|
||||
deleteIconSmall: {
|
||||
width: `${theme.spacing(2)}px`,
|
||||
height: `${theme.spacing(2)}px`,
|
||||
margin: `0 ${theme.spacing(0.5)}px 0 -${theme.spacing(0.5)}px`,
|
||||
},
|
||||
},
|
||||
MuiCardHeader: {
|
||||
root: {
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-router": "6.0.0-beta.0",
|
||||
"react-router-dom": "6.0.0-beta.0",
|
||||
"react-use": "^15.3.3",
|
||||
"swagger-ui-react": "^3.31.1"
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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 { ComponentEntity, Entity } from '@backstage/catalog-model';
|
||||
import { Progress } from '@backstage/core';
|
||||
import React, { FC } from 'react';
|
||||
import { Grid } from '@material-ui/core';
|
||||
import {
|
||||
ApiDefinitionCard,
|
||||
useComponentApiEntities,
|
||||
useComponentApiNames,
|
||||
} from '../../components';
|
||||
|
||||
export const EntityPageApi: FC<{ entity: Entity }> = ({ entity }) => {
|
||||
const apiNames = useComponentApiNames(entity as ComponentEntity);
|
||||
|
||||
const { apiEntities, loading } = useComponentApiEntities({
|
||||
entity: entity as ComponentEntity,
|
||||
});
|
||||
|
||||
if (loading) {
|
||||
return <Progress />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid container spacing={3}>
|
||||
{apiNames.map(api => (
|
||||
<Grid item xs={12} key={api}>
|
||||
<ApiDefinitionCard title={api} apiEntity={apiEntities!.get(api)} />
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { EntityPageApi } from './EntityPageApi';
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { Route, Routes } from 'react-router';
|
||||
import { WarningPanel } from '@backstage/core';
|
||||
import { catalogRoute } from '../routes';
|
||||
import { EntityPageApi } from './EntityPageApi';
|
||||
|
||||
const isPluginApplicableToEntity = (entity: Entity) => {
|
||||
return ((entity.spec?.implementsApis as string[]) || []).length > 0;
|
||||
};
|
||||
|
||||
export const Router = ({ entity }: { entity: Entity }) =>
|
||||
// TODO(shmidt-i): move warning to a separate standardized component
|
||||
!isPluginApplicableToEntity(entity) ? (
|
||||
<WarningPanel title="API Docs plugin:">
|
||||
The entity doesn't implement any APIs.
|
||||
</WarningPanel>
|
||||
) : (
|
||||
<Routes>
|
||||
<Route
|
||||
path={`/${catalogRoute.path}`}
|
||||
element={<EntityPageApi entity={entity} />}
|
||||
/>
|
||||
)
|
||||
</Routes>
|
||||
);
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { Router } from './Router';
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { ApiProvider, ApiRegistry, storageApiRef } from '@backstage/core';
|
||||
// TODO: Circular ref!
|
||||
import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog';
|
||||
import { MockStorageApi, wrapInTestApp } from '@backstage/test-utils';
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
@@ -15,11 +15,10 @@
|
||||
*/
|
||||
|
||||
import { Content, useApi } from '@backstage/core';
|
||||
// TODO: Circular ref
|
||||
import { catalogApiRef } from '@backstage/plugin-catalog';
|
||||
import React from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
import { ApiCatalogTable } from '../ApiCatalogTable/ApiCatalogTable';
|
||||
import { ApiCatalogTable } from '../ApiCatalogTable';
|
||||
import ApiCatalogLayout from './ApiCatalogLayout';
|
||||
|
||||
const CatalogPageContents = () => {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { ApiCatalogPage } from './ApiCatalogPage';
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { ApiCatalogTable } from './ApiCatalogTable';
|
||||
@@ -14,23 +14,32 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ApiEntityV1alpha1 } from '@backstage/catalog-model';
|
||||
import { ApiEntity } from '@backstage/catalog-model';
|
||||
import { InfoCard } from '@backstage/core';
|
||||
import React from 'react';
|
||||
import { ApiDefinitionWidget } from '../ApiDefinitionWidget/ApiDefinitionWidget';
|
||||
import { ApiDefinitionWidget } from '../ApiDefinitionWidget';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
|
||||
type Props = {
|
||||
title?: string;
|
||||
apiEntity: ApiEntityV1alpha1;
|
||||
apiEntity?: ApiEntity;
|
||||
};
|
||||
|
||||
export const ApiDefinitionCard = ({ title, apiEntity }: Props) => {
|
||||
const type = apiEntity?.spec?.type || '';
|
||||
const definition = apiEntity?.spec?.definition || '';
|
||||
if (!apiEntity) {
|
||||
return (
|
||||
<InfoCard title={title}>
|
||||
<Alert severity="error">Could not fetch the API</Alert>
|
||||
</InfoCard>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<InfoCard title={title} subheader={type}>
|
||||
<ApiDefinitionWidget type={type} definition={definition} />
|
||||
<InfoCard title={title} subheader={apiEntity.spec.type}>
|
||||
<ApiDefinitionWidget
|
||||
type={apiEntity.spec.type}
|
||||
definition={apiEntity.spec.definition}
|
||||
/>
|
||||
</InfoCard>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { ApiDefinitionCard } from './ApiDefinitionCard';
|
||||
@@ -15,9 +15,9 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { AsyncApiDefinitionWidget } from '../AsyncApiDefinitionWidget/AsyncApiDefinitionWidget';
|
||||
import { OpenApiDefinitionWidget } from '../OpenApiDefinitionWidget/OpenApiDefinitionWidget';
|
||||
import { PlainApiDefinitionWidget } from '../PlainApiDefinitionWidget/PlainApiDefinitionWidget';
|
||||
import { AsyncApiDefinitionWidget } from '../AsyncApiDefinitionWidget';
|
||||
import { OpenApiDefinitionWidget } from '../OpenApiDefinitionWidget';
|
||||
import { PlainApiDefinitionWidget } from '../PlainApiDefinitionWidget';
|
||||
|
||||
type Props = {
|
||||
type: string;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user