Index.ts reflects .action(actionhandler()) change
This commit is contained in:
+4
-13
@@ -1,15 +1,6 @@
|
||||
const path = require('path');
|
||||
const base = require('@backstage/cli/config/eslint');
|
||||
|
||||
module.exports = {
|
||||
...base,
|
||||
rules: {
|
||||
...base.rules,
|
||||
'notice/notice': [
|
||||
'error',
|
||||
{
|
||||
templateFile: path.resolve(__dirname, 'scripts/copyright.js'),
|
||||
},
|
||||
],
|
||||
},
|
||||
extends: [
|
||||
require.resolve('@backstage/cli/config/eslint.js'),
|
||||
'@spotify/eslint-config-oss',
|
||||
],
|
||||
};
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
name: 'UX Component'
|
||||
about: 'For designers to request UX components to be added to the Backstage Storybook'
|
||||
---
|
||||
|
||||
## 🗒 General
|
||||
<!--- Write a nice note to the community requesting the creation of a new component! -->
|
||||
<!--- Include an image of your component. Bonus points for a GIF! -->
|
||||
|
||||
## 💻 Usage
|
||||
<!--- Tell us what the point of this component/pattern is! How does it help? How should it work? Any rules? -->
|
||||
|
||||
## 📐 Specs
|
||||
<!--- Include images that detail the redlines for your component.-->
|
||||
<!--- Once we get our Figma workspace set up, we'll be posting the Figma files rather than doing specs by hand.-->
|
||||
|
||||
## 🔮 Future
|
||||
<!--- Any upcoming, exciting functionality for this component in the future? List that out here. -->
|
||||
-
|
||||
-
|
||||
-
|
||||
-
|
||||
-
|
||||
@@ -6,7 +6,6 @@ on:
|
||||
- '.github/workflows/cli.yml'
|
||||
- 'packages/cli/**'
|
||||
- 'packages/core/**'
|
||||
types: [opened, reopened, edited, synchronize]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
@@ -2,7 +2,6 @@ name: Frontend CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, edited, synchronize]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
FROM nginx:mainline
|
||||
|
||||
# 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`
|
||||
|
||||
COPY packages/app/build /usr/share/nginx/html
|
||||
COPY docker/default.conf.template /etc/nginx/conf.d/default.conf.template
|
||||
COPY docker/run.sh /usr/local/bin/run.sh
|
||||
|
||||
@@ -13,7 +13,7 @@ Backstage is an open platform for building developer portals.
|
||||
|
||||
The philosophy behind Backstage is simple: Don't expose your engineers to the full complexity of your infrastructure tooling. Engineers should be shipping code — not figuring out a whole new toolset every time they want to implement the basics. Backstage allows you add "stuff" (tooling, services, features, etc.) by adding a plugin, instead of building a new tool. This saves you work and avoids the need of your team to learn how to use and support yet another tool.
|
||||
|
||||
For more information go to [backstage.io](https://backstage.io)
|
||||
For more information go to [backstage.io](https://backstage.io) or join our [Discord chatroom](https://discord.gg/EBHEGzX).
|
||||
|
||||
## What problem does Backstage solve?
|
||||
|
||||
@@ -41,7 +41,7 @@ Our vision for Backstage is for it to become the trusted standard toolbox (read:
|
||||
|
||||
The Backstage platform consists of a number of different components:
|
||||
|
||||
- **frontend** - Main web application that users interact with. It's built up by a number of different _Plugins_.
|
||||
- **app** - Main web application that users interact with. It's built up by a number of different _Plugins_. This repo contains an example implementation of an app (located in `packages/example-app`) and you can easily get started with your own app by [creating one](docs/getting-started/create-an-app.md).
|
||||
- **plugins** - Each plugin is treated as a self-contained web app and can include almost any type of content. Plugins all use a common set of platform API's and reusable UI components. Plugins can fetch data either from the _backend_ or through any RESTful API exposed through the _proxy_.
|
||||
- **backend** \* - GraphQL aggregation service that holds the model of your software ecosystem, including organisational information and what team owns what software. The backend also has a Plugin model for extending its graph.
|
||||
- **proxy** \* - Terminates HTTPS and exposes any RESTful API to Plugins.
|
||||
@@ -53,7 +53,7 @@ _\* not yet released_
|
||||
|
||||
## Getting started
|
||||
|
||||
To run the Backstage frontend, you will need to have the following installed:
|
||||
To run a Backstage app, you will need to have the following installed:
|
||||
|
||||
- [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
|
||||
- [NodeJS](https://nodejs.org/en/download/) - Active LTS Release, currently v12
|
||||
@@ -75,16 +75,17 @@ For more complex development environment configuration, see the
|
||||
## Documentation
|
||||
|
||||
- [FAQs](docs/FAQ.md)
|
||||
- [Create a Plugin](docs/getting-started/create-a-plugin.md)
|
||||
- [Structure of a Plugin](docs/getting-started/structure-of-a-plugin.md)
|
||||
- [Frontend architecture](docs/architecture-terminology.md)
|
||||
- [Getting Started](docs/getting-started/README.md)
|
||||
- [Architecture](docs/architecture-terminology.md)
|
||||
- [API references](docs/reference/README.md)
|
||||
- [Designing for Backstage](docs/design.md)
|
||||
- [Storybook - UI components](http://storybook.backstage.io) ([WIP](https://github.com/spotify/backstage/milestone/9))
|
||||
- [Contributing to Storybook](docs/getting-started/contributing-to-storybook.md)
|
||||
- Using Backstage components (TODO)
|
||||
|
||||
## Community
|
||||
|
||||
- [Discord chat](https://discord.gg/MUpMjP2) - Get support or discuss the project
|
||||
- [Discord chatroom](https://discord.gg/MUpMjP2) - Get support or discuss the project
|
||||
- [Good First Issues](https://github.com/spotify/backstage/labels/good%20first%20issue) - Start here if you want to contribute
|
||||
- [RFCs](https://github.com/spotify/backstage/labels/rfc) - Help shape the technical direction
|
||||
- [FAQ](docs/FAQ.md) - Frequently Asked Questions
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 86 KiB |
+8
-4
@@ -1,6 +1,6 @@
|
||||
# FAQ
|
||||
|
||||
## Why Material-UI?
|
||||
## Q: Why Material-UI?
|
||||
|
||||
The short answer is that it's what we've been using in Backstage internally.
|
||||
|
||||
@@ -13,7 +13,7 @@ focus of Backstage is to make plugin developers productive with as few hurdles a
|
||||
possible, material-ui lets plugin makers both get going easily with well-known tech
|
||||
and a large flora of components.
|
||||
|
||||
## Are you planning on having plugins cooked into the repo or should they be developed in separate repos?
|
||||
## Q: Are you planning on having plugins cooked into the repo or should they be developed in separate repos?
|
||||
|
||||
Additional open sourced plugins would be added to the `plugins` directory in this monorepo.
|
||||
|
||||
@@ -21,7 +21,7 @@ While we encourage using the open soure model, integrators that want to experime
|
||||
Backstage internally may also choose to develop closed source plugins in a manner that suits
|
||||
them best, for example in their respective Backstage source repository.
|
||||
|
||||
## Any plans for integrating with other repository managers such as Gitlab or Bitbucket?
|
||||
## Q: Any plans for integrating with other repository managers such as Gitlab or Bitbucket?
|
||||
|
||||
We chose Github by the fact that it is the tool that we are most familiar with and that will naturally
|
||||
lead to integrations for Github specifically being developed in an early stage.
|
||||
@@ -32,7 +32,7 @@ as well. Hopefully contributed by the community.
|
||||
|
||||
And note that implementations of Backstage can be hosted wherever you feel suits your needs best.
|
||||
|
||||
## Can Backstage by used for other things than developer portals?
|
||||
## Q: Can Backstage by used for other things than developer portals?
|
||||
|
||||
Yes.
|
||||
|
||||
@@ -40,3 +40,7 @@ The core frontend framework could be used for building any large-scale web appli
|
||||
|
||||
That being said, in [Phase 2](https://github.com/spotify/backstage#project-roadmap) of the project we will add features that are needed for developer portals and systems for managing software ecosystems. Our ambition will be to keep Backstage modular.
|
||||
|
||||
## Q: My company doesn't have thousands of developers. Is Backstage overkill?
|
||||
|
||||
Not really. Sure, having something like Backstage gets more important as the number of developers in your company grows. One of the core reasons to adopt Backstage is to help standardise how software is built at your company. Setting guidelines and deciding on standards is easier when your company is smaller.
|
||||
|
||||
|
||||
@@ -6,3 +6,4 @@ Check out [https://backstage.io]() or see the table of content below.
|
||||
- [Getting started](getting-started/README.md)
|
||||
- [References](reference/README.md)
|
||||
- [Publishing](publishing.md)
|
||||
- [Designing for Backstage](design.md)
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
# Designing for Backstage
|
||||
|
||||
Much like Backstage Open Source, this is a *living* document! We'll keep this updated as we evolve our practices!
|
||||
|
||||
## 📚 Our Philosophy
|
||||
|
||||
### Iterative
|
||||
Backstage Open Source is a newly launched endeavor, and we’re excited to scale up our design practices! With that said, we’ll be working closely with you, the community, and iterating and experimenting as we go to see what works best. As a continual work in progress, we aspire to release early and often. Not only that, we are committed to working with developers to create a seamless and easy handoff. If you’re curious to see how we grow and would like to play a role in that growth, check out the issues in this GitHub repo!
|
||||
|
||||
|
||||
### Collaborative
|
||||
The Backstage Design Team is small but mighty, and we truly cherish the amazing opportunity we have to work with the Backstage Open Source community! Have an idea? A component request? Feel free to communicate with us via [Discord](https://discord.gg/PefUsZ) (*#design* channel). Collaboration trumps individual speed, and we want to work with you to make Backstage work for all of our users.
|
||||
|
||||
|
||||
### Transparent
|
||||
There are a lot of exciting things coming up and we want to keep you in the loop! Keep an eye on our Milestones in GitHub to see where we’re headed. We’ll also be posting updates in the *#design* channel on [Discord](https://discord.gg/PefUsZ). Not only that, we want to keep you informed on the decisions we’ve made and why we’ve made them.
|
||||
|
||||
|
||||
## 🛠 Our Practice
|
||||
The chart below details how we work. ***Stay tuned***: We are currently in the process of securing a Figma workspace for Backstage Open Source, and we plan on referencing Figma documents to share specs and prototypes with the community.
|
||||
|
||||
| Step 1 | Step 2 | Step 3 | Step 4 | Step 5 | Step 6 |
|
||||
|:---|:---|:---|:---|:---|:---|
|
||||
| Platform design team submits an issue to **spotify/Backstage GitHub** with a potential component. | Backstage community offers feedback or approval on **spotify/Backstage GitHub**. | Platform design team adjusts accordingly (as they see fit) and update the Figma DLS document. | Designed component is added to **spotify/Backstage GitHub** as an issue. | External or internal Backstage open source contributors build the component. | External or internal contributors add the component to the **Backstage Storybook**. 🎉 |
|
||||
|
||||
|
||||
|
||||
The following diagram shows the relationship between the Backstage Design System and our foundation, which comprises of [Material UI](https://material-ui.com/) that is shaped by user experience and user interface decisions made by our Backstage Design Team. Also note, we encourage you to take the core experience we’ve crafted and add custom theming to better represent your organization!
|
||||
|
||||

|
||||
|
||||
|
||||
## ✅ Our Priorities
|
||||
### Backstage Design System
|
||||
This is the set of building blocks for Backstage contributors to leverage as they create rad plugins for Backstage! Why reinvent the wheel when you can use components that have already been vetted by our team and the Backstage community? In the spirit of crafting a cohesive and consistent user experience across all of Backstage, we strongly urge all plugin developers to utilize our Storybook as a reference. Our design system is new and evolving, and we’ll be building it up with your help!
|
||||
### Core Backstage User Experience
|
||||
This is the universal user experience that is shared amongst all Backstage users. From more concrete aspects like the plugins marketplace to more abstract ones like end-to-end workflows on Backstage, we’ll be working with the community to create a core user experience that best serves you and your organization.
|
||||
|
||||
## ⭐️ How to Contribute
|
||||
### Pick up an issue!
|
||||
In the beginning, most of our issues will be centered around creating universal components for our Backstage Design System and adding them to our Storybook so plugin developers can reference them. We’ll also be creating issues that are focused on building up our core Backstage user experience. We’ll be labeling our issues in GitHub with ‘design’ and/or ‘storybook’ - so feel free to browse and tackle the tasks that interest you. If you have any questions regarding an issue, you can ask them in the comments section of the issue or on [Discord](https://discord.gg/PefUsZ). We absolutely adore our external contributors and will send you virtual semlas for your contributions!
|
||||
|
||||
### Request a component.
|
||||
Create an issue (label it design and assign it to katz95) or send us a message on [Discord](https://discord.gg/PefUsZ) (*#design* channel) with details of what the component is and its relevant use cases. Your request will be reviewed by our design team and you should hear back from us within 1-2 business days. We’ll get back to you and let you know whether your requested component will get picked up by our team as something to be added to our design system.
|
||||
|
||||
## ✏️ Resources
|
||||
**[Storybook](http://storybook.backstage.io/)** - where you can view our 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.
|
||||
|
||||
**[Discord](https://discord.gg/PefUsZ)** - all design questions should be directed to the *#design* channel.
|
||||
|
||||
**Documentation**
|
||||
- Patterns (stay tuned)
|
||||
- Figma files/libraries (stay tuned)
|
||||
|
||||
## 🔮 Future
|
||||
### Contributions from designers
|
||||
Are you a designer at an organisation that’s implementing Backstage? A designer who’s fascinated by the developer productivity problem space? A designer who’s curious about open source design? We’d love for you to contribute. Behind the scenes, we’re setting up a few foundational elements to make sure that contributing to Backstage as a designer is easy. From styling guidelines to UX principles to Figma documents, we’ll make sure you’re equipped to chip in on this project. We’re excited to work with you! In the meantime, we’d love to hear from you on [Discord](https://discord.gg/PefUsZ).
|
||||
|
||||
|
||||
[Back to Docs](../README.md)
|
||||
@@ -3,6 +3,7 @@
|
||||
Here is a collection of tutorials that will guide you through setting up and extending an instance of Backstage with your own plugins.
|
||||
|
||||
- [Development Environment](development-environment.md)
|
||||
- [Create a Backstage App](create-an-app.md)
|
||||
- [Create a Backstage plugin](create-a-plugin.md)
|
||||
- [Structure of a plugin](structure-of-a-plugin.md)
|
||||
- Using Backstage components (TODO)
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
# Contributing to Storybook
|
||||
|
||||
## Creating a new Story
|
||||
|
||||
> A Story basically represents a single visual state of a component.
|
||||
|
||||
To create a new story, create a new file located alongside the component you want to document on Storybook.
|
||||
|
||||
See below an example of the structure:
|
||||
|
||||
```
|
||||
core
|
||||
└── src
|
||||
└── components
|
||||
└── Progress
|
||||
├── Progress.tsx
|
||||
└── Progress.stories.tsx
|
||||
```
|
||||
|
||||
> _Note: make sure your component story file has the following format componentName.stories.tsx_
|
||||
|
||||
## Running locally
|
||||
|
||||
Go to `packages/storybook`, run `yarn install` and install the dependencies, then run the following on your command line: `yarn start`
|
||||
|
||||

|
||||
|
||||
_You should see a log like the image above._
|
||||
|
||||
If everything worked out, your server will be running on **port 6006**, go to your browser and navigate to `http://localhost:6006/`. You should be able to navigate and see the Storybook page.
|
||||
|
||||

|
||||
@@ -1,12 +1,18 @@
|
||||
# Create a plugin
|
||||
# Backstage Plugins
|
||||
|
||||
To create a new plugin, make sure you've run `yarn` and installed dependencies, then run the following on your command line (invoking the `backstage-cli`).
|
||||
A Backstage Plugin adds functionality to Backstage.
|
||||
|
||||
## Create a plugin
|
||||
|
||||
To create a new plugin, make sure you've run `yarn install` and installed dependencies, then run the following on your command line (invoking the `backstage-cli`).
|
||||
|
||||
```bash
|
||||
yarn create-plugin
|
||||
```
|
||||
|
||||

|
||||
<p align='center'>
|
||||
<img src='https://github.com/spotify/backstage/raw/master/docs/getting-started/create-plugin_output.png' width='600' alt='create plugin'>
|
||||
</p>
|
||||
|
||||
This will create a new Backstage Plugin based on the ID that was provided. It will be built and
|
||||
added to the Backstage App automatically.
|
||||
@@ -14,7 +20,9 @@ added to the Backstage App automatically.
|
||||
_If `yarn start` is already running you should be able to see the default page for your new
|
||||
plugin directly by navigating to `http://localhost:3000/my-plugin`._
|
||||
|
||||

|
||||
<p align='center'>
|
||||
<img src='https://github.com/spotify/backstage/raw/master/docs/getting-started/my-plugin_screenshot.png' width='600' alt='my plugin'>
|
||||
</p>
|
||||
|
||||
[Next Step - Structure of a plugin](structure-of-a-plugin.md)
|
||||
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
# Backstage App
|
||||
|
||||
To get set up quickly with your own Backstage project you can create a Backstage App.
|
||||
|
||||
A Backstage App is a monorepo setup with `lerna` that includes everything you need to run Backstage in your own environment.
|
||||
|
||||
## Create an app
|
||||
|
||||
To create a Backstage app, you will need to have [NodeJS](https://nodejs.org/en/download/) Active LTS Release installed (currently v12).
|
||||
|
||||
With `npx`:
|
||||
|
||||
```bash
|
||||
npx @backstage/cli create-app
|
||||
```
|
||||
|
||||
This will create a new Backstage App inside the current folder. The name of the app-folder is the name that was provided when prompted.
|
||||
|
||||
<p align='center'>
|
||||
<img src='https://github.com/spotify/backstage/raw/master/docs/getting-started/create-app_output.png' width='600' alt='create app'>
|
||||
</p>
|
||||
|
||||
Inside that directory, it will generate all the files and folder structure needed for you to run your app.
|
||||
|
||||
### Folder structure
|
||||
|
||||
```
|
||||
app
|
||||
├── README.md
|
||||
├── lerna.json
|
||||
├── package.json
|
||||
├── prettier.config.js
|
||||
├── tsconfig.json
|
||||
├── packages
|
||||
│ └── app
|
||||
│ ├── package.json
|
||||
│ ├── tsconfig.json
|
||||
│ ├── public
|
||||
│ │ └── ...
|
||||
│ └── src
|
||||
│ ├── App.test.tsx
|
||||
│ ├── App.tsx
|
||||
│ ├── index.tsx
|
||||
│ ├── plugins.ts
|
||||
│ └── setupTests.ts
|
||||
└── plugins
|
||||
└── welcome
|
||||
├── README.md
|
||||
├── package.json
|
||||
├── tsconfig.json
|
||||
└── src
|
||||
├── index.ts
|
||||
├── plugin.test.ts
|
||||
├── plugin.ts
|
||||
├── setupTests.ts
|
||||
└── components
|
||||
├── Timer
|
||||
│ └── ...
|
||||
└── WelcomePage
|
||||
└── ...
|
||||
```
|
||||
|
||||
## Run the app
|
||||
|
||||
When the installation is complete you can open the app folder and start the app.
|
||||
|
||||
```bash
|
||||
cd my-backstage-app
|
||||
yarn start
|
||||
```
|
||||
|
||||
_When `yarn start` is ready it should open up a browser window displaying your app, if not you can navigate to `http://localhost:3000`._
|
||||
|
||||
[Next Step - Create a Backstage plugin](create-a-plugin.md)
|
||||
|
||||
[Back to Docs](README.md)
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 303 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
+1
-1
@@ -5,5 +5,5 @@
|
||||
],
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"version": "0.1.1-alpha.1"
|
||||
"version": "0.1.1-alpha.2"
|
||||
}
|
||||
|
||||
+4
-2
@@ -5,11 +5,13 @@
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "yarn build && yarn workspace example-app start",
|
||||
"start": "yarn workspace example-app start",
|
||||
"bundle": "yarn build && yarn workspace example-app bundle",
|
||||
"build": "lerna run build",
|
||||
"test": "yarn build && lerna run test --since origin/master -- --coverage",
|
||||
"test:all": "yarn build && lerna run test -- --coverage",
|
||||
"lint": "lerna run lint --since origin/master --",
|
||||
"lint:all": "lerna run lint --",
|
||||
"docker-build": "yarn bundle && docker build . -t spotify/backstage",
|
||||
"create-plugin": "backstage-cli create-plugin",
|
||||
"remove-plugin": "backstage-cli remove-plugin",
|
||||
@@ -25,8 +27,8 @@
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"devDependencies": {
|
||||
"@spotify/eslint-config-oss": "^1.0.1",
|
||||
"cross-env": "^7.0.0",
|
||||
"eslint-plugin-notice": "^0.8.9",
|
||||
"husky": "^4.2.3",
|
||||
"lerna": "^3.20.2",
|
||||
"lint-staged": "^10.1.0",
|
||||
|
||||
@@ -7,6 +7,15 @@
|
||||
{
|
||||
"assertFunctionNames": ["expect", "cy.contains"]
|
||||
}
|
||||
],
|
||||
"import/no-extraneous-dependencies": [
|
||||
"error",
|
||||
{
|
||||
"devDependencies": true,
|
||||
"optionalDependencies": true,
|
||||
"peerDependencies": true,
|
||||
"bundledDependencies": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
{
|
||||
"name": "example-app",
|
||||
"version": "0.1.1-alpha.1",
|
||||
"version": "0.1.1-alpha.2",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.1",
|
||||
"@backstage/core": "^0.1.1-alpha.1",
|
||||
"@backstage/plugin-home-page": "^0.1.1-alpha.1",
|
||||
"@backstage/plugin-welcome": "^0.1.1-alpha.1",
|
||||
"@backstage/cli": "^0.1.1-alpha.2",
|
||||
"@backstage/core": "^0.1.1-alpha.2",
|
||||
"@backstage/theme": "^0.1.1-alpha.2",
|
||||
"@backstage/plugin-home-page": "^0.1.1-alpha.2",
|
||||
"@backstage/plugin-welcome": "^0.1.1-alpha.2",
|
||||
"@material-ui/core": "^4.9.1",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
@@ -18,6 +19,7 @@
|
||||
"@types/react-router-dom": "^5.1.3",
|
||||
"@types/zen-observable": "^0.8.0",
|
||||
"cross-env": "^7.0.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^16.12.0",
|
||||
"react-dom": "^16.12.0",
|
||||
"react-router-dom": "^5.1.2",
|
||||
|
||||
@@ -14,12 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { CssBaseline, makeStyles, ThemeProvider } from '@material-ui/core';
|
||||
import {
|
||||
BackstageThemeLight,
|
||||
BackstageThemeDark,
|
||||
createApp,
|
||||
} from '@backstage/core';
|
||||
CssBaseline,
|
||||
makeStyles,
|
||||
Theme,
|
||||
ThemeProvider,
|
||||
} from '@material-ui/core';
|
||||
import { BackstageThemeLight, BackstageThemeDark } from '@backstage/theme';
|
||||
import { createApp } from '@backstage/core';
|
||||
import React, { FC } from 'react';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import Root from './components/Root';
|
||||
@@ -51,13 +53,15 @@ app.registerApis(apis);
|
||||
app.registerPlugin(...Object.values(plugins));
|
||||
const AppComponent = app.build();
|
||||
|
||||
type T = typeof BackstageThemeLight | typeof BackstageThemeDark;
|
||||
|
||||
const App: FC<{}> = () => {
|
||||
useStyles();
|
||||
const [theme, toggleTheme] = useThemeType(
|
||||
localStorage.getItem('theme') || 'auto',
|
||||
);
|
||||
|
||||
let backstageTheme = BackstageThemeLight;
|
||||
let backstageTheme: T = BackstageThemeLight;
|
||||
switch (theme) {
|
||||
case 'light':
|
||||
backstageTheme = BackstageThemeLight;
|
||||
@@ -82,7 +86,7 @@ const App: FC<{}> = () => {
|
||||
};
|
||||
return (
|
||||
<ThemeContext.Provider value={themeContext}>
|
||||
<ThemeProvider theme={backstageTheme}>
|
||||
<ThemeProvider theme={backstageTheme as Theme}>
|
||||
<CssBaseline>
|
||||
<ErrorDisplay forwarder={errorDialogForwarder} />
|
||||
<Router>
|
||||
|
||||
@@ -1,16 +1,87 @@
|
||||
#!/usr/bin/env node
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
|
||||
// Figure out whether we're running inside the backstage repo or as an installed dependency
|
||||
const isLocal = require('fs').existsSync(path.resolve(__dirname, '../src'));
|
||||
if (!isLocal) {
|
||||
// src-relative imports are a pain to get to work with plain tsc compilation, as the
|
||||
// transpiled code will maintain the imports as they are in the source. Which means an
|
||||
// import for `helpers/paths` will start like that in the output, which won't work in NodeJS.
|
||||
//
|
||||
// This is a solution for getting src-relative imports to work with typescript/node in
|
||||
// the published package. We're using `module: "amd"` to ship the entire cli implementation
|
||||
// in one file, and it also happens to generate correct module definition and import statements.
|
||||
// Minimal AMD implementation
|
||||
const moduleFactories = {};
|
||||
const moduleCache = {};
|
||||
global.define = (name, deps, moduleFunc) => {
|
||||
moduleFactories[name] = () => {
|
||||
const exportsObj = {};
|
||||
|
||||
// require() that first searches for locally defined amd modules
|
||||
const requireFunc = name => {
|
||||
let factory = moduleFactories[name];
|
||||
if (!factory) {
|
||||
// Check /index as well, to mirror nodejs resolution
|
||||
const index = `${name}/index`;
|
||||
if (!moduleFactories[index]) {
|
||||
return require(name);
|
||||
}
|
||||
name = index;
|
||||
factory = moduleFactories[name];
|
||||
}
|
||||
if (!moduleCache[name]) {
|
||||
moduleCache[name] = factory();
|
||||
}
|
||||
return moduleCache[name];
|
||||
};
|
||||
const impls = deps.slice(2).map(requireFunc);
|
||||
|
||||
moduleFunc(requireFunc, exportsObj, ...impls);
|
||||
|
||||
return exportsObj;
|
||||
};
|
||||
};
|
||||
require('../dist');
|
||||
|
||||
// index module is the entrypoint
|
||||
moduleFactories.index();
|
||||
} else {
|
||||
const tsConfigPath = path.resolve(__dirname, '../tsconfig.json');
|
||||
const {
|
||||
baseUrl: relativeBaseUrl,
|
||||
paths = {},
|
||||
} = require(tsConfigPath).compilerOptions;
|
||||
const baseUrl = path.resolve(__dirname, '..', relativeBaseUrl);
|
||||
|
||||
// Use tsconfig-paths to resolve src-relative paths during development
|
||||
const cleanupPaths = require('tsconfig-paths').register({ baseUrl, paths });
|
||||
|
||||
require('ts-node').register({
|
||||
project: path.resolve(__dirname, '../tsconfig.json'),
|
||||
compilerOptions: {
|
||||
module: 'CommonJS',
|
||||
},
|
||||
transpileOnly: true,
|
||||
});
|
||||
|
||||
require('../src');
|
||||
|
||||
cleanupPaths();
|
||||
}
|
||||
|
||||
@@ -23,9 +23,10 @@ module.exports = {
|
||||
'prettier/react',
|
||||
'prettier/@typescript-eslint',
|
||||
'plugin:jest/recommended',
|
||||
'plugin:monorepo/recommended',
|
||||
],
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['notice'],
|
||||
plugins: ['notice', 'import'],
|
||||
env: {
|
||||
jest: true,
|
||||
},
|
||||
@@ -39,6 +40,18 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
ignorePatterns: ['**/dist/**', '**/build/**'],
|
||||
rules: {
|
||||
'import/no-duplicates': 'warn',
|
||||
'import/no-extraneous-dependencies': [
|
||||
'error',
|
||||
{
|
||||
devDependencies: false,
|
||||
optionalDependencies: true,
|
||||
peerDependencies: true,
|
||||
bundledDependencies: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['**/*.ts?(x)'],
|
||||
@@ -47,5 +60,20 @@ module.exports = {
|
||||
'react/prop-types': 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.test.*', '**/src/setupTests.*'],
|
||||
rules: {
|
||||
// Tests are allowed to import dev dependencies
|
||||
'import/no-extraneous-dependencies': [
|
||||
'error',
|
||||
{
|
||||
devDependencies: true,
|
||||
optionalDependencies: true,
|
||||
peerDependencies: true,
|
||||
bundledDependencies: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -24,7 +24,9 @@ if (fs.existsSync('jest.config.js')) {
|
||||
} else if (fs.existsSync('jest.config.ts')) {
|
||||
module.exports = require(path.resolve('jest.config.ts'));
|
||||
} else {
|
||||
const extraOptions = {};
|
||||
const extraOptions = {
|
||||
modulePaths: ['<rootDir>'],
|
||||
};
|
||||
|
||||
// Use src/setupTests.ts as the default location for configuring test env
|
||||
if (fs.existsSync('src/setupTests.ts')) {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "@spotify/web-scripts/config/tsconfig.json",
|
||||
"exclude": ["**/*.test.*"],
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"noEmit": false,
|
||||
"declarationMap": true,
|
||||
"incremental": true,
|
||||
"module": "es6"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/cli",
|
||||
"description": "CLI for developing Backstage plugins and apps",
|
||||
"version": "0.1.1-alpha.1",
|
||||
"version": "0.1.1-alpha.2",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
@@ -19,7 +19,7 @@
|
||||
"main": "dist",
|
||||
"scripts": {
|
||||
"exec": "npx ts-node ./src",
|
||||
"build": "backstage-cli build-cache -- tsc --outDir dist --noEmit false --module CommonJS",
|
||||
"build": "backstage-cli build-cache -- tsc",
|
||||
"lint": "backstage-cli lint",
|
||||
"test": "backstage-cli test",
|
||||
"start": "nodemon ."
|
||||
@@ -39,8 +39,8 @@
|
||||
"@types/webpack-dev-server": "^3.10.0",
|
||||
"del": "^5.1.0",
|
||||
"nodemon": "^2.0.2",
|
||||
"tar": "^6.0.1",
|
||||
"ts-node": "^8.6.2"
|
||||
"ts-node": "^8.6.2",
|
||||
"tsconfig-paths": "^3.9.0"
|
||||
},
|
||||
"bin": {
|
||||
"backstage-cli": "bin/backstage-cli"
|
||||
@@ -52,9 +52,12 @@
|
||||
"@rollup/plugin-json": "^4.0.2",
|
||||
"@rollup/plugin-node-resolve": "^7.1.1",
|
||||
"@spotify/web-scripts": "^6.0.0",
|
||||
"chalk": "^4.0.0",
|
||||
"chokidar": "^3.3.1",
|
||||
"commander": "^4.1.1",
|
||||
"dashify": "^2.0.0",
|
||||
"eslint-plugin-import": "^2.20.2",
|
||||
"eslint-plugin-monorepo": "^0.2.1",
|
||||
"fork-ts-checker-webpack-plugin": "^4.0.5",
|
||||
"fs-extra": "^8.1.0",
|
||||
"handlebars": "^4.7.3",
|
||||
@@ -70,10 +73,14 @@
|
||||
"rollup-plugin-peer-deps-external": "^2.2.2",
|
||||
"rollup-plugin-postcss": "^2.5.0",
|
||||
"rollup-plugin-typescript2": "^0.26.0",
|
||||
"tar": "^6.0.1",
|
||||
"ts-loader": "^6.2.1",
|
||||
"webpack": "^4.41.6",
|
||||
"webpack-dev-server": "^3.10.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0"
|
||||
},
|
||||
"files": [
|
||||
"templates",
|
||||
"config",
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { run } from '../../helpers/run';
|
||||
import { run } from 'helpers/run';
|
||||
|
||||
export default async () => {
|
||||
const args = ['build'];
|
||||
|
||||
@@ -14,18 +14,29 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { run } from '../../helpers/run';
|
||||
import { watchDeps } from '../watch-deps';
|
||||
import { spawn } from 'child_process';
|
||||
import { waitForExit } from 'helpers/run';
|
||||
import { watchDeps } from 'commands/watch-deps';
|
||||
import { createLogger } from 'commands/watch-deps/logger';
|
||||
|
||||
export default async () => {
|
||||
const args = ['start'];
|
||||
|
||||
// Start dynamic watch and build of dependencies, then serve the app
|
||||
await watchDeps();
|
||||
await run('react-scripts', args, {
|
||||
await watchDeps({ build: true });
|
||||
|
||||
const child = spawn('react-scripts', ['start'], {
|
||||
env: {
|
||||
FORCE_COLOR: 'true',
|
||||
EXTEND_ESLINT: 'true',
|
||||
SKIP_PREFLIGHT_CHECK: 'true',
|
||||
...process.env,
|
||||
},
|
||||
stdio: ['inherit', 'pipe', 'inherit'],
|
||||
shell: true,
|
||||
});
|
||||
|
||||
// We need to avoid clearing the terminal, or the build feedback of dependencies will be lost
|
||||
const log = createLogger();
|
||||
child.stdout.setEncoding('utf8');
|
||||
child.stdout!.on('data', log.out);
|
||||
await waitForExit(child);
|
||||
};
|
||||
|
||||
@@ -16,10 +16,11 @@
|
||||
|
||||
import fs from 'fs-extra';
|
||||
import { resolve as resolvePath, relative as relativePath } from 'path';
|
||||
import { runPlain, runCheck } from '../../helpers/run';
|
||||
import { runPlain, runCheck } from 'helpers/run';
|
||||
import { Options } from './options';
|
||||
import { extractArchive, createArchive } from './archive';
|
||||
import { paths } from '../../helpers/paths';
|
||||
import { paths } from 'helpers/paths';
|
||||
import { version, isDev } from 'helpers/version';
|
||||
|
||||
const INFO_FILE = '.backstage-build-cache';
|
||||
|
||||
@@ -74,7 +75,14 @@ export class Cache {
|
||||
static async readInputKey(
|
||||
inputPaths: string[],
|
||||
): Promise<CacheKey | undefined> {
|
||||
const quotedInputPaths = inputPaths.map(input => `'${input}'`);
|
||||
const allInputPaths = inputPaths.slice();
|
||||
|
||||
// If we're executing the cli inside the backstage repo, we add the cli src as cache key as well
|
||||
if (isDev) {
|
||||
allInputPaths.unshift(paths.ownDir);
|
||||
}
|
||||
|
||||
const quotedInputPaths = allInputPaths.map(input => `'${input}'`);
|
||||
|
||||
// Make sure we don't have any uncommitted changes to the input, in that case we skip caching.
|
||||
const noChanges = await runCheck(
|
||||
@@ -88,9 +96,18 @@ export class Cache {
|
||||
for (const quotedInputPath of quotedInputPaths) {
|
||||
const output = await runPlain(`git ls-tree HEAD ${quotedInputPath}`);
|
||||
const [, , sha] = output.split(/\s+/, 3);
|
||||
// If we can't get a tree sha it means we're outside of tracked files, so treat as dirty
|
||||
if (!sha) {
|
||||
return undefined;
|
||||
}
|
||||
trees.push(sha);
|
||||
}
|
||||
return trees;
|
||||
|
||||
if (isDev) {
|
||||
return trees;
|
||||
}
|
||||
// If we're executing as a dependency, use the version as a key
|
||||
return [version, ...trees];
|
||||
}
|
||||
|
||||
constructor(
|
||||
|
||||
@@ -14,27 +14,26 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import fs from 'fs-extra';
|
||||
import { Command } from 'commander';
|
||||
import { run } from '../../helpers/run';
|
||||
import { run } from 'helpers/run';
|
||||
import { Cache } from './cache';
|
||||
import { parseOptions } from './options';
|
||||
import { parseOptions, Options } from './options';
|
||||
|
||||
function print(msg: string) {
|
||||
process.stdout.write(`[build-cache] ${msg}\n`);
|
||||
}
|
||||
|
||||
/*
|
||||
* The build-cache command is used to make builds a no-op if there are no changes to the package.
|
||||
* It supports both local development where the output directory remains intact, as well as CI
|
||||
* where the output directory is stored in a separate cache dir.
|
||||
*/
|
||||
export default async (cmd: Command, args: string[]) => {
|
||||
const options = await parseOptions(cmd);
|
||||
|
||||
// Wrap a build function with a cache, which won't call the build function on cache hit
|
||||
export async function withCache(
|
||||
options: Options,
|
||||
buildFunc: () => Promise<void>,
|
||||
): Promise<void> {
|
||||
const key = await Cache.readInputKey(options.inputs);
|
||||
if (!key) {
|
||||
print('input directory is dirty, skipping cache');
|
||||
await run(args[0], args.slice(1));
|
||||
await fs.remove(options.output);
|
||||
await buildFunc();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -52,9 +51,23 @@ export default async (cmd: Command, args: string[]) => {
|
||||
}
|
||||
|
||||
print('cache miss, need to build');
|
||||
await fs.remove(options.output);
|
||||
await buildFunc();
|
||||
|
||||
await run(args[0], args.slice(1));
|
||||
|
||||
print('caching build output');
|
||||
await cacheResult.archive(options.output, options.maxCacheEntries);
|
||||
}
|
||||
|
||||
/*
|
||||
* The build-cache command is used to make builds a no-op if there are no changes to the package.
|
||||
* It supports both local development where the output directory remains intact, as well as CI
|
||||
* where the output directory is stored in a separate cache dir.
|
||||
*/
|
||||
export default async (cmd: Command, args: string[]) => {
|
||||
const options = await parseOptions(cmd);
|
||||
|
||||
await withCache(options, async () => {
|
||||
await run(args[0], args.slice(1));
|
||||
});
|
||||
};
|
||||
|
||||
export * from './options';
|
||||
|
||||
@@ -16,8 +16,9 @@
|
||||
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import { Command } from 'commander';
|
||||
import { paths } from '../../helpers/paths';
|
||||
import { paths } from 'helpers/paths';
|
||||
|
||||
const DEFAULT_CACHE_DIR = '<repoRoot>/node_modules/.cache/backstage-builds';
|
||||
const DEFAULT_MAX_ENTRIES = 10;
|
||||
|
||||
export type Options = {
|
||||
@@ -27,19 +28,34 @@ export type Options = {
|
||||
maxCacheEntries: number;
|
||||
};
|
||||
|
||||
export async function parseOptions(cmd: Command): Promise<Options> {
|
||||
const argTransformer = (arg: string) =>
|
||||
resolvePath(arg.replace(/<repoRoot>/g, paths.targetRoot).replace(/'/g, ''));
|
||||
function transformPath(path: string): string {
|
||||
return resolvePath(
|
||||
path.replace(/<repoRoot>/g, paths.targetRoot).replace(/'/g, ''),
|
||||
);
|
||||
}
|
||||
|
||||
const inputs = cmd.input.map(argTransformer) as string[];
|
||||
export async function parseOptions(cmd: Command): Promise<Options> {
|
||||
const inputs = cmd.input.map(transformPath) as string[];
|
||||
if (inputs.length === 0) {
|
||||
inputs.push(argTransformer('.'));
|
||||
inputs.push(transformPath('.'));
|
||||
}
|
||||
const output = argTransformer(cmd.output);
|
||||
const cacheDir = argTransformer(
|
||||
const output = transformPath(cmd.output);
|
||||
const cacheDir = transformPath(
|
||||
process.env.BACKSTAGE_CACHE_DIR || cmd.cacheDir,
|
||||
);
|
||||
const maxCacheEntries =
|
||||
Number(process.env.BACKSTAGE_CACHE_MAX_ENTRIES) || DEFAULT_MAX_ENTRIES;
|
||||
return { inputs, output, cacheDir, maxCacheEntries };
|
||||
}
|
||||
|
||||
export function getDefaultCacheOptions(): Options {
|
||||
return {
|
||||
inputs: [transformPath('.')],
|
||||
output: transformPath('dist'),
|
||||
cacheDir: transformPath(
|
||||
process.env.BACKSTAGE_CACHE_DIR || DEFAULT_CACHE_DIR,
|
||||
),
|
||||
maxCacheEntries:
|
||||
Number(process.env.BACKSTAGE_CACHE_MAX_ENTRIES) || DEFAULT_MAX_ENTRIES,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -21,8 +21,9 @@ import inquirer, { Answers, Question } from 'inquirer';
|
||||
import { exec as execCb } from 'child_process';
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import os from 'os';
|
||||
import { Task, templatingTask } from '../../helpers/tasks';
|
||||
import { paths } from '../../helpers/paths';
|
||||
import { Task, templatingTask } from 'helpers/tasks';
|
||||
import { paths } from 'helpers/paths';
|
||||
import { version } from 'helpers/version';
|
||||
const exec = promisify(execCb);
|
||||
|
||||
async function checkExists(rootDir: string, name: string) {
|
||||
@@ -108,7 +109,6 @@ export default async () => {
|
||||
const templateDir = paths.resolveOwn('templates/default-app');
|
||||
const tempDir = resolvePath(os.tmpdir(), answers.name);
|
||||
const appDir = resolvePath(paths.targetDir, answers.name);
|
||||
const version = require(paths.resolveOwn('package.json')).version;
|
||||
|
||||
Task.log();
|
||||
Task.log('Creating the app...');
|
||||
|
||||
@@ -26,8 +26,9 @@ import {
|
||||
addCodeownersEntry,
|
||||
getCodeownersFilePath,
|
||||
} from './lib/codeowners';
|
||||
import { paths } from '../../helpers/paths';
|
||||
import { Task, templatingTask } from '../../helpers/tasks';
|
||||
import { paths } from 'helpers/paths';
|
||||
import { version } from 'helpers/version';
|
||||
import { Task, templatingTask } from 'helpers/tasks';
|
||||
const exec = promisify(execCb);
|
||||
|
||||
async function checkExists(rootDir: string, id: string) {
|
||||
@@ -86,7 +87,7 @@ const addExportStatement = async (
|
||||
export async function addPluginDependencyToApp(
|
||||
rootDir: string,
|
||||
pluginName: string,
|
||||
version: string,
|
||||
versionStr: string,
|
||||
) {
|
||||
const pluginPackage = `@backstage/plugin-${pluginName}`;
|
||||
const packageFilePath = 'packages/app/package.json';
|
||||
@@ -103,7 +104,7 @@ export async function addPluginDependencyToApp(
|
||||
);
|
||||
}
|
||||
|
||||
dependencies[pluginPackage] = `^${version}`;
|
||||
dependencies[pluginPackage] = `^${versionStr}`;
|
||||
packageFileJson.dependencies = sortObjectByKeys(dependencies);
|
||||
const newContents = `${JSON.stringify(packageFileJson, null, 2)}\n`;
|
||||
|
||||
@@ -223,7 +224,6 @@ export default async () => {
|
||||
const templateDir = paths.resolveOwn('templates/default-plugin');
|
||||
const tempDir = resolvePath(os.tmpdir(), answers.id);
|
||||
const pluginDir = paths.resolveTargetRoot('plugins', answers.id);
|
||||
const version = require(paths.resolveOwn('package.json')).version;
|
||||
const ownerIds = parseOwnerIds(answers.owner);
|
||||
|
||||
Task.log();
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import { Command } from 'commander';
|
||||
import { run } from '../helpers/run';
|
||||
import { run } from 'helpers/run';
|
||||
|
||||
export default async (cmd: Command) => {
|
||||
const args = ['lint', '--max-warnings=0', '--format=codeframe'];
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import { rollup, watch, OutputOptions } from 'rollup';
|
||||
import conf from './rollup.config';
|
||||
import { Command } from 'commander';
|
||||
import { withCache, getDefaultCacheOptions } from 'commands/build-cache';
|
||||
|
||||
export default async (cmd: Command) => {
|
||||
if (cmd.watch) {
|
||||
@@ -40,10 +41,12 @@ export default async (cmd: Command) => {
|
||||
});
|
||||
}
|
||||
|
||||
const bundle = await rollup({
|
||||
input: conf.input,
|
||||
plugins: conf.plugins,
|
||||
await withCache(getDefaultCacheOptions(), async () => {
|
||||
const bundle = await rollup({
|
||||
input: conf.input,
|
||||
plugins: conf.plugins,
|
||||
});
|
||||
await bundle.generate(conf.output as OutputOptions);
|
||||
await bundle.write(conf.output as OutputOptions);
|
||||
});
|
||||
await bundle.generate(conf.output as OutputOptions);
|
||||
await bundle.write(conf.output as OutputOptions);
|
||||
};
|
||||
|
||||
@@ -22,6 +22,7 @@ import postcss from 'rollup-plugin-postcss';
|
||||
import imageFiles from 'rollup-plugin-image-files';
|
||||
import json from '@rollup/plugin-json';
|
||||
import { RollupWatchOptions } from 'rollup';
|
||||
import { paths } from 'helpers/paths';
|
||||
|
||||
export default {
|
||||
input: 'src/index.ts',
|
||||
@@ -30,7 +31,9 @@ export default {
|
||||
format: 'cjs',
|
||||
},
|
||||
plugins: [
|
||||
peerDepsExternal(),
|
||||
peerDepsExternal({
|
||||
includeDependencies: true,
|
||||
}),
|
||||
resolve({
|
||||
mainFields: ['browser', 'module', 'main'],
|
||||
}),
|
||||
@@ -41,6 +44,8 @@ export default {
|
||||
postcss(),
|
||||
imageFiles(),
|
||||
json(),
|
||||
typescript(),
|
||||
typescript({
|
||||
include: `${paths.resolveTarget('src')}/**/*.{js,jsx,ts,tsx}`,
|
||||
}),
|
||||
],
|
||||
} as RollupWatchOptions;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import { Command } from 'commander';
|
||||
import { run } from '../../helpers/run';
|
||||
import { run } from 'helpers/run';
|
||||
|
||||
export default async (cmd: Command) => {
|
||||
const args = ['test'];
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
*/
|
||||
|
||||
import { Command } from 'commander';
|
||||
import { run } from '../helpers/run';
|
||||
import { paths } from '../helpers/paths';
|
||||
import { run } from 'helpers/run';
|
||||
import { paths } from 'helpers/paths';
|
||||
|
||||
export default async (cmd: Command) => {
|
||||
const args = ['test', '--config', paths.resolveOwn('config/jest.js')];
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
*/
|
||||
|
||||
import chalk from 'chalk';
|
||||
|
||||
import fs from 'fs-extra';
|
||||
import { createLoggerFactory } from './logger';
|
||||
import { getPackageDeps } from './packages';
|
||||
import { findAllDeps } from './packages';
|
||||
import { startWatcher, startPackageWatcher } from './watcher';
|
||||
import { startCompiler } from './compiler';
|
||||
import { startChild } from './child';
|
||||
import { waitForExit } from '../../helpers/run';
|
||||
import { paths } from '../../helpers/paths';
|
||||
import { waitForExit, run } from 'helpers/run';
|
||||
import { paths } from 'helpers/paths';
|
||||
|
||||
const PACKAGE_BLACKLIST = [
|
||||
// We never want to watch for changes in the cli, but all packages will depend on it.
|
||||
@@ -31,9 +31,13 @@ const PACKAGE_BLACKLIST = [
|
||||
|
||||
const WATCH_LOCATIONS = ['package.json', 'src', 'assets'];
|
||||
|
||||
export type Options = {
|
||||
build?: boolean;
|
||||
};
|
||||
|
||||
// Start watching for dependency changes.
|
||||
// The returned promise resolves when watchers have started for all current dependencies.
|
||||
export async function watchDeps() {
|
||||
export async function watchDeps(options: Options = {}) {
|
||||
const localPackagePath = paths.resolveTarget('package.json');
|
||||
|
||||
// Rotate through different prefix colors to make it easier to differenciate between different deps
|
||||
@@ -46,7 +50,21 @@ export async function watchDeps() {
|
||||
]);
|
||||
|
||||
// Find all direct and transitive local dependencies of the current package.
|
||||
const deps = await getPackageDeps(localPackagePath, PACKAGE_BLACKLIST);
|
||||
const packageData = await fs.readFile(localPackagePath, 'utf8');
|
||||
const packageJson = JSON.parse(packageData);
|
||||
const packageName = packageJson.name;
|
||||
|
||||
const deps = await findAllDeps(packageName, PACKAGE_BLACKLIST);
|
||||
|
||||
if (options.build) {
|
||||
await run('lerna', [
|
||||
'run',
|
||||
'--scope',
|
||||
packageName,
|
||||
'--include-dependencies',
|
||||
'build',
|
||||
]);
|
||||
}
|
||||
|
||||
// We lazily watch all our deps, as in we don't start the actual watch compiler until a change is detected
|
||||
const watcher = await startWatcher(deps, WATCH_LOCATIONS, pkg => {
|
||||
@@ -56,7 +74,7 @@ export async function watchDeps() {
|
||||
});
|
||||
|
||||
await startPackageWatcher(localPackagePath, async () => {
|
||||
const newDeps = await getPackageDeps(localPackagePath, PACKAGE_BLACKLIST);
|
||||
const newDeps = await findAllDeps(packageName, PACKAGE_BLACKLIST);
|
||||
await watcher.update(newDeps);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,11 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import fs from 'fs';
|
||||
import { promisify } from 'util';
|
||||
import { paths } from '../../helpers/paths';
|
||||
|
||||
const readFile = promisify(fs.readFile);
|
||||
import { paths } from 'helpers/paths';
|
||||
|
||||
const LernaProject = require('@lerna/project');
|
||||
const PackageGraph = require('@lerna/package-graph');
|
||||
@@ -64,10 +60,3 @@ export async function findAllDeps(
|
||||
|
||||
return [...deps.values()];
|
||||
}
|
||||
|
||||
export async function getPackageDeps(packagePath: string, blacklist: string[]) {
|
||||
const packageData = await readFile(packagePath, 'utf8');
|
||||
const packageJson = JSON.parse(packageData);
|
||||
|
||||
return await findAllDeps(packageJson.name, blacklist);
|
||||
}
|
||||
|
||||
@@ -77,8 +77,22 @@ export function findRootPath(topPath: string): string {
|
||||
);
|
||||
}
|
||||
|
||||
// Finds the root of the cli package itself
|
||||
export function findOwnDir() {
|
||||
// Known relative locations of package in dist/dev
|
||||
const pathDist = '..';
|
||||
const pathDev = '../..';
|
||||
|
||||
// Check the closest dir first
|
||||
const pkgInDist = resolvePath(__dirname, pathDist, 'package.json');
|
||||
const isDist = fs.pathExistsSync(pkgInDist);
|
||||
|
||||
const path = isDist ? pathDist : pathDev;
|
||||
return resolvePath(__dirname, path);
|
||||
}
|
||||
|
||||
export function findPaths(): Paths {
|
||||
const ownDir = resolvePath(__dirname, '../..');
|
||||
const ownDir = findOwnDir();
|
||||
const targetDir = fs.realpathSync(process.cwd());
|
||||
|
||||
// We're not always running in a monorepo, so we lazy init this to only crash commands
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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 fs from 'fs-extra';
|
||||
import { paths } from './paths';
|
||||
|
||||
export function findVersion() {
|
||||
const pkgContent = fs.readFileSync(paths.resolveOwn('package.json'), 'utf8');
|
||||
return JSON.parse(pkgContent).version;
|
||||
}
|
||||
|
||||
export const version = findVersion();
|
||||
export const isDev = fs.pathExistsSync(paths.resolveOwn('src'));
|
||||
+19
-27
@@ -16,78 +16,69 @@
|
||||
|
||||
import program from 'commander';
|
||||
import chalk from 'chalk';
|
||||
import createAppCommand from './commands/create-app/createApp';
|
||||
import createPluginCommand from './commands/create-plugin/createPlugin';
|
||||
import removePluginCommand from './commands/remove-plugin/removePlugin';
|
||||
import watch from './commands/watch-deps';
|
||||
import buildCache from './commands/build-cache';
|
||||
import lintCommand from './commands/lint';
|
||||
import testCommand from './commands/testCommand';
|
||||
import appBuild from './commands/app/build';
|
||||
import appServe from './commands/app/serve';
|
||||
import pluginBuild from './commands/plugin/build';
|
||||
import pluginServe from './commands/plugin/serve';
|
||||
import { exitWithError } from './helpers/errors';
|
||||
import { paths } from './helpers/paths';
|
||||
import { exitWithError } from 'helpers/errors';
|
||||
import { version } from 'helpers/version';
|
||||
|
||||
const main = (argv: string[]) => {
|
||||
const version = require(paths.resolveOwn('package.json')).version;
|
||||
|
||||
program.name('backstage-cli').version(version);
|
||||
|
||||
program
|
||||
.command('create-app')
|
||||
.description('Creates a new app in a new directory')
|
||||
.action(actionHandler(createAppCommand));
|
||||
.action(actionHandler(() => require('commands/create-app/createApp')));
|
||||
|
||||
program
|
||||
.command('app:build')
|
||||
.description('Build an app for a production release')
|
||||
.action(actionHandler(appBuild));
|
||||
.action(actionHandler(() => require('commands/app/build')));
|
||||
|
||||
program
|
||||
.command('app:serve')
|
||||
.description('Serve an app for local development')
|
||||
.action(actionHandler(appServe));
|
||||
.action(actionHandler(() => require('commands/app/serve')));
|
||||
|
||||
program
|
||||
.command('create-plugin')
|
||||
.description('Creates a new plugin in the current repository')
|
||||
.action(actionHandler(createPluginCommand));
|
||||
.action(
|
||||
actionHandler(() => require('commands/create-plugin/createPlugin')),
|
||||
);
|
||||
|
||||
program
|
||||
.command('remove-plugin')
|
||||
.description('Removes plugin in the current repository')
|
||||
.action(actionHandler(removePluginCommand));
|
||||
.action(
|
||||
actionHandler(() => require('commands/remove-plugin/removePlugin')),
|
||||
);
|
||||
|
||||
program
|
||||
.command('plugin:build')
|
||||
.option('--watch', 'Enable watch mode')
|
||||
.description('Build a plugin')
|
||||
.action(actionHandler(pluginBuild));
|
||||
.action(actionHandler(() => require('commands/plugin/build')));
|
||||
|
||||
program
|
||||
.command('plugin:serve')
|
||||
.description('Serves the dev/ folder of a plugin')
|
||||
.action(actionHandler(pluginServe));
|
||||
.action(actionHandler(() => require('commands/plugin/serve')));
|
||||
|
||||
program
|
||||
.command('lint')
|
||||
.option('--fix', 'Attempt to automatically fix violations')
|
||||
.description('Lint a package')
|
||||
.action(actionHandler(lintCommand));
|
||||
.action(actionHandler(() => require('commands/lint')));
|
||||
|
||||
program
|
||||
.command('test')
|
||||
.option('--watch', 'Enable watch mode')
|
||||
.option('--coverage', 'Report test coverage')
|
||||
.description('Run all tests for package')
|
||||
.action(actionHandler(testCommand));
|
||||
.action(actionHandler(() => require('commands/testCommand')));
|
||||
|
||||
program
|
||||
.command('watch-deps')
|
||||
.description('Watch all dependencies while running another command')
|
||||
.action(actionHandler(watch));
|
||||
.action(actionHandler(() => require('commands/watch-deps')));
|
||||
|
||||
program
|
||||
.command('build-cache')
|
||||
@@ -104,7 +95,7 @@ const main = (argv: string[]) => {
|
||||
'Cache dir',
|
||||
'<repoRoot>/node_modules/.cache/backstage-builds',
|
||||
)
|
||||
.action(actionHandler(buildCache));
|
||||
.action(actionHandler(() => require('commands/build-cache')));
|
||||
|
||||
program.on('command:*', () => {
|
||||
console.log();
|
||||
@@ -125,10 +116,11 @@ const main = (argv: string[]) => {
|
||||
|
||||
// Wraps an action function so that it always exits and handles errors
|
||||
function actionHandler<T extends readonly any[]>(
|
||||
actionFunc: (...args: T) => Promise<any>,
|
||||
actionRequireFunc: () => { default(...args: T): Promise<any> },
|
||||
): (...args: T) => Promise<never> {
|
||||
return async (...args: T) => {
|
||||
try {
|
||||
const actionFunc = actionRequireFunc().default;
|
||||
await actionFunc(...args);
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { CssBaseline, makeStyles, ThemeProvider } from '@material-ui/core';
|
||||
import { BackstageTheme, createApp } from '@backstage/core';
|
||||
import { createApp } from '@backstage/core';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import React, { FC } from 'react';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import * as plugins from './plugins';
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "plugin-welcome",
|
||||
"version": "0.0.0",
|
||||
"main": "dist/cjs/index.js",
|
||||
"types": "dist/cjs/index.d.ts",
|
||||
"main": "dist/index.cjs.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "backstage-cli plugin:build",
|
||||
@@ -10,11 +10,18 @@
|
||||
"test": "backstage-cli test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/core": "^{{version}}",
|
||||
"@backstage/cli": "^{{version}}",
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.3.2",
|
||||
"@types/testing-library__jest-dom": "5.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@material-ui/lab": "4.0.0-alpha.45"
|
||||
"@backstage/core": "^{{version}}",
|
||||
"@backstage/theme": "^{{version}}",
|
||||
"@material-ui/core": "^4.9.1",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"react": "16.13.1",
|
||||
"react-dom": "16.13.1",
|
||||
"react-router-dom": "5.1.2"
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import WelcomePage from './WelcomePage';
|
||||
import { ThemeProvider } from '@material-ui/core';
|
||||
import { BackstageTheme } from '@backstage/core';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
|
||||
describe('WelcomePage', () => {
|
||||
it('should render', () => {
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ const WelcomePage: FC<{}> = () => {
|
||||
</ContentHeader>
|
||||
<Grid container>
|
||||
<Grid item xs={12} md={6}>
|
||||
<InfoCard maxWidth>
|
||||
<InfoCard>
|
||||
<Typography variant="body1" gutterBottom>
|
||||
You now have a running instance of Backstage!
|
||||
<span role="img" aria-label="confetti">
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
{
|
||||
"extends": "@spotify/web-scripts/config/tsconfig.json",
|
||||
"exclude": ["**/*.test.*"],
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"noEmit": false,
|
||||
"declarationMap": true,
|
||||
"incremental": true
|
||||
}
|
||||
"extends": "@backstage/cli/config/tsconfig.json"
|
||||
}
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
"license": "Apache-2.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build:watch": "backstage-cli plugin:build --watch",
|
||||
"build": "backstage-cli build-cache -- backstage-cli plugin:build",
|
||||
"build": "backstage-cli plugin:build",
|
||||
"lint": "backstage-cli lint",
|
||||
"test": "backstage-cli test"
|
||||
},
|
||||
@@ -23,10 +22,12 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@backstage/core": "^{{version}}",
|
||||
"@backstage/theme": "^{{version}}",
|
||||
"@material-ui/core": "^4.9.1",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
"react": "16.13.1",
|
||||
"react-dom": "16.13.1"
|
||||
"react-dom": "16.13.1",
|
||||
"react-use": "^13.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ import { render } from '@testing-library/react';
|
||||
import mockFetch from 'jest-fetch-mock';
|
||||
import ExampleComponent from './ExampleComponent';
|
||||
import { ThemeProvider } from '@material-ui/core';
|
||||
import { BackstageTheme } from '@backstage/core';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
|
||||
describe('ExampleComponent', () => {
|
||||
it('should render', () => {
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
{
|
||||
"extends": "@spotify/web-scripts/config/tsconfig.json",
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": ["src"],
|
||||
"compilerOptions": {
|
||||
"baseUrl": "src",
|
||||
"paths": {
|
||||
"*": ["src/*"]
|
||||
}
|
||||
"outFile": "dist/index.js",
|
||||
"module": "amd",
|
||||
"baseUrl": "src"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/core",
|
||||
"description": "Core API used by Backstage plugins and apps",
|
||||
"version": "0.1.1-alpha.1",
|
||||
"version": "0.1.1-alpha.2",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
@@ -16,11 +16,10 @@
|
||||
"backstage"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"main": "dist/cjs/index.js",
|
||||
"types": "dist/cjs/index.d.ts",
|
||||
"main": "dist/index.cjs.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build:watch": "tsc --outDir dist/cjs --noEmit false --module CommonJS --watch",
|
||||
"build": "backstage-cli build-cache -- tsc --outDir dist/cjs --noEmit false --module CommonJS",
|
||||
"build": "backstage-cli plugin:build",
|
||||
"lint": "backstage-cli lint",
|
||||
"test": "backstage-cli test"
|
||||
},
|
||||
@@ -31,6 +30,8 @@
|
||||
"@types/jest": "^24.0.0",
|
||||
"@types/node": "^12.0.0",
|
||||
"classnames": "^2.2.6",
|
||||
"clsx": "^1.1.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"rc-progress": "^2.5.2",
|
||||
"react": "^16.12.0",
|
||||
"react-addons-text-content": "0.0.4",
|
||||
@@ -40,10 +41,15 @@
|
||||
"recompose": "0.30.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.1",
|
||||
"@backstage/cli": "^0.1.1-alpha.2",
|
||||
"@backstage/test-utils": "^0.1.1-alpha.2",
|
||||
"@backstage/theme": "^0.1.1-alpha.2",
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.3.2",
|
||||
"@testing-library/user-event": "^7.1.2",
|
||||
"prop-types": "^15.7.2"
|
||||
"react-router": "^5.1.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@backstage/theme": "^0.1.1-alpha.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import ApiProvider, { useApi, withApis } from './ApiProvider';
|
||||
import ApiRef from './ApiRef';
|
||||
import ApiRegistry from './ApiRegistry';
|
||||
import { render } from '@testing-library/react';
|
||||
import { withLogCollector } from '../../testUtils';
|
||||
import { withLogCollector } from '@backstage/test-utils';
|
||||
|
||||
describe('ApiProvider', () => {
|
||||
type Api = () => string;
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { wrapInThemedTestApp } from '../testUtils';
|
||||
import { COLORS } from '@backstage/theme';
|
||||
import { wrapInThemedTestApp } from '@backstage/test-utils';
|
||||
import CircleProgress, { getProgressColor } from './CircleProgress';
|
||||
import { COLORS } from '../theme/BackstageTheme';
|
||||
|
||||
describe('<CircleProgress />', () => {
|
||||
it('renders without exploding', () => {
|
||||
|
||||
@@ -15,12 +15,11 @@
|
||||
*/
|
||||
|
||||
import { makeStyles } from '@material-ui/core';
|
||||
import { BackstageTheme, COLORS } from '@backstage/theme';
|
||||
import { Circle } from 'rc-progress';
|
||||
import React, { FC } from 'react';
|
||||
import { COLORS } from '../theme/BackstageTheme';
|
||||
import { BackstageTheme } from '../theme/theme';
|
||||
|
||||
const useStyles = makeStyles((theme: BackstageTheme) => ({
|
||||
const useStyles = makeStyles<typeof BackstageTheme>(theme => ({
|
||||
root: {
|
||||
position: 'relative',
|
||||
lineHeight: 0,
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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 HorizontalScrollGrid from './HorizontalScrollGrid';
|
||||
|
||||
const cardContentStyle = { height: 0, padding: 150, margin: 20 };
|
||||
const containerStyle = { width: 800, height: 400, margin: 20 };
|
||||
const opacityArray = [0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0];
|
||||
|
||||
export default {
|
||||
title: 'HorizontalScrollGrid',
|
||||
component: HorizontalScrollGrid,
|
||||
};
|
||||
|
||||
export const Default = () => (
|
||||
<div style={containerStyle}>
|
||||
<HorizontalScrollGrid>
|
||||
{opacityArray.map(element => {
|
||||
const style = { backgroundColor: `rgba(0, 185, 151, ${element})` };
|
||||
return <div style={{ ...style, ...cardContentStyle }} key={element} />;
|
||||
})}
|
||||
</HorizontalScrollGrid>
|
||||
</div>
|
||||
);
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import { render, fireEvent } from '@testing-library/react';
|
||||
import { renderWithEffects, wrapInThemedTestApp } from '../testUtils';
|
||||
import { renderWithEffects, wrapInThemedTestApp } from '@backstage/test-utils';
|
||||
import HorizontalScrollGrid from './HorizontalScrollGrid';
|
||||
import { Grid } from '@material-ui/core';
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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 { AlphaLabel } from './Lifecycle';
|
||||
|
||||
export default {
|
||||
title: 'Alpha Lifecycle',
|
||||
component: AlphaLabel,
|
||||
};
|
||||
|
||||
export const Default = () => (
|
||||
<>
|
||||
This feature is in <AlphaLabel />
|
||||
</>
|
||||
);
|
||||
export const Shorthand = () => (
|
||||
<>
|
||||
This feature is in <AlphaLabel isShorthand />
|
||||
</>
|
||||
);
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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 { BetaLabel } from './Lifecycle';
|
||||
|
||||
export default {
|
||||
title: 'Beta Lifecycle',
|
||||
component: BetaLabel,
|
||||
};
|
||||
|
||||
export const Default = () => (
|
||||
<>
|
||||
This feature is in <BetaLabel />
|
||||
</>
|
||||
);
|
||||
export const Shorthand = () => (
|
||||
<>
|
||||
This feature is in <BetaLabel isShorthand />
|
||||
</>
|
||||
);
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* 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 ProgressCard from './ProgressCard';
|
||||
import { Grid } from '@material-ui/core';
|
||||
|
||||
const linkInfo = { title: 'Go to XYZ Location', link: '#' };
|
||||
|
||||
export default {
|
||||
title: 'Progress Card',
|
||||
component: ProgressCard,
|
||||
};
|
||||
|
||||
export const Default = () => (
|
||||
<Grid container spacing={2}>
|
||||
<Grid item>
|
||||
<ProgressCard title="Progress" progress={0.3} />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<ProgressCard title="Progress" progress={0.57} />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<ProgressCard title="Progress" progress={0.89} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
|
||||
export const Subhead = () => (
|
||||
<Grid container spacing={2}>
|
||||
<Grid item>
|
||||
<ProgressCard
|
||||
title="Progress"
|
||||
subheader="With a subheader"
|
||||
progress={0.3}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<ProgressCard
|
||||
title="Progress"
|
||||
subheader="With a subheader"
|
||||
progress={0.57}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<ProgressCard
|
||||
title="Progress"
|
||||
subheader="With a subheader"
|
||||
progress={0.89}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
|
||||
export const LinkInFooter = () => (
|
||||
<Grid container spacing={2}>
|
||||
<Grid item>
|
||||
<ProgressCard title="Progress" deepLink={linkInfo} progress={0.3} />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<ProgressCard title="Progress" deepLink={linkInfo} progress={0.57} />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<ProgressCard title="Progress" deepLink={linkInfo} progress={0.89} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { wrapInThemedTestApp } from '../testUtils';
|
||||
import { wrapInThemedTestApp } from '@backstage/test-utils';
|
||||
|
||||
import ProgressCard from './ProgressCard';
|
||||
|
||||
|
||||
@@ -18,15 +18,16 @@ import React, { FC } from 'react';
|
||||
import { makeStyles } from '@material-ui/core';
|
||||
|
||||
import InfoCard from '../layout/InfoCard';
|
||||
import { Props as BottomLinkProps } from '../layout/InfoCard/BottomLink';
|
||||
import CircleProgress from './CircleProgress';
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
subheader?: string;
|
||||
variant?: string;
|
||||
progress: number | string;
|
||||
deepLink?: string | { title: string; link: string };
|
||||
gacontext?: string;
|
||||
/** Progress in % specified as decimal, e.g. "0.23" */
|
||||
progress: number;
|
||||
deepLink?: BottomLinkProps;
|
||||
};
|
||||
|
||||
const useStyles = makeStyles({
|
||||
@@ -40,25 +41,15 @@ const ProgressCard: FC<Props> = props => {
|
||||
const classes = useStyles(props);
|
||||
const { title, subheader, progress, deepLink, variant } = props;
|
||||
|
||||
// TODO(freben): Make type more strict when InfoCard is written in TS
|
||||
let link: any = undefined;
|
||||
if (deepLink) {
|
||||
if (typeof deepLink === 'string') {
|
||||
link = { title: 'View more', link: deepLink };
|
||||
} else {
|
||||
link = deepLink;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<InfoCard
|
||||
title={title}
|
||||
subheader={subheader}
|
||||
deepLink={link}
|
||||
deepLink={deepLink}
|
||||
variant={variant}
|
||||
>
|
||||
<CircleProgress value={Number(progress)} />
|
||||
<CircleProgress value={progress} />
|
||||
</InfoCard>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
*/
|
||||
|
||||
import { makeStyles } from '@material-ui/core';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import classNames from 'classnames';
|
||||
import React, { FC } from 'react';
|
||||
import { BackstageTheme } from '../../theme/theme';
|
||||
|
||||
const useStyles = makeStyles((theme: BackstageTheme) => ({
|
||||
const useStyles = makeStyles<typeof BackstageTheme>(theme => ({
|
||||
status: {
|
||||
width: 12,
|
||||
height: 12,
|
||||
|
||||
@@ -18,7 +18,7 @@ import { SvgIconProps } from '@material-ui/core';
|
||||
import PeopleIcon from '@material-ui/icons/People';
|
||||
import PersonIcon from '@material-ui/icons/Person';
|
||||
import React, { FC } from 'react';
|
||||
import { useApp } from '../api';
|
||||
import { useApp } from '../api/app/AppContext';
|
||||
import { IconComponent, SystemIconKey, SystemIcons } from './types';
|
||||
|
||||
export const defaultSystemIcons: SystemIcons = {
|
||||
|
||||
@@ -24,14 +24,11 @@ export { default as HeaderLabel } from './layout/HeaderLabel';
|
||||
export { default as InfoCard } from './layout/InfoCard';
|
||||
export { default as ErrorBoundary } from './layout/ErrorBoundary';
|
||||
export * from './layout/Sidebar';
|
||||
export { default as BackstageThemeLight } from './theme/BackstageThemeLight';
|
||||
export { default as BackstageTheme } from './theme/BackstageTheme';
|
||||
export { COLORS } from './theme/BackstageTheme';
|
||||
export { default as BackstageThemeDark } from './theme/BackstageThemeDark';
|
||||
export { default as HorizontalScrollGrid } from './components/HorizontalScrollGrid';
|
||||
export { default as ProgressCard } from './components/ProgressCard';
|
||||
export { default as CircleProgress } from './components/CircleProgress';
|
||||
export { default as Progress } from './components/Progress';
|
||||
export { AlphaLabel, BetaLabel } from './components/Lifecycle';
|
||||
export { default as SupportButton } from './components/SupportButton';
|
||||
export { default as SortableTable } from './components/SortableTable';
|
||||
export { FeatureCalloutCircular } from './components/FeatureDiscovery/FeatureCalloutCircular';
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import ContentHeader from './ContentHeader';
|
||||
import { wrapInThemedTestApp } from '../../testUtils';
|
||||
import { wrapInThemedTestApp } from '@backstage/test-utils';
|
||||
|
||||
jest.mock('react-helmet', () => {
|
||||
return ({ defaultTitle }) => <div>defaultTitle: {defaultTitle}</div>;
|
||||
|
||||
@@ -21,9 +21,8 @@
|
||||
import React, { ComponentType, Fragment, FC } from 'react';
|
||||
import { Typography, makeStyles } from '@material-ui/core';
|
||||
import Helmet from 'react-helmet';
|
||||
import { BackstageTheme } from '../../theme/theme';
|
||||
|
||||
const useStyles = makeStyles<BackstageTheme>(theme => ({
|
||||
const useStyles = makeStyles(theme => ({
|
||||
container: {
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import React, { ComponentClass, Component, SFC } from 'react';
|
||||
|
||||
type Props = {
|
||||
slackChannel: string;
|
||||
slackChannel?: string;
|
||||
onError?: (error: Error, errorInfo: string) => null;
|
||||
};
|
||||
|
||||
@@ -69,13 +69,14 @@ export default ErrorBoundary;
|
||||
type EProps = {
|
||||
error?: Error;
|
||||
errorInfo?: string;
|
||||
slackChannel: string;
|
||||
slackChannel?: string;
|
||||
};
|
||||
|
||||
const Error: SFC<EProps> = ({ slackChannel }) => {
|
||||
return (
|
||||
<div>
|
||||
Something went wrong here. Please contact {slackChannel} for help.
|
||||
Something went wrong here.{' '}
|
||||
{slackChannel && <>Please contact {slackChannel} for help.</>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { wrapInThemedTestApp } from '@backstage/test-utils';
|
||||
import Header from './Header';
|
||||
import { wrapInThemedTestApp } from '../../testUtils';
|
||||
|
||||
jest.mock('react-helmet', () => {
|
||||
return ({ defaultTitle }) => <div>defaultTitle: {defaultTitle}</div>;
|
||||
|
||||
@@ -17,12 +17,13 @@
|
||||
import React, { Fragment, ReactNode, CSSProperties, FC } from 'react';
|
||||
import Helmet from 'react-helmet';
|
||||
import { Typography, Tooltip, makeStyles } from '@material-ui/core';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
|
||||
import { Theme } from '../Page/Page';
|
||||
// import { Link } from 'shared/components';
|
||||
import { BackstageTheme } from '../../theme/theme';
|
||||
import Waves from './Waves';
|
||||
|
||||
const useStyles = makeStyles<BackstageTheme>(theme => ({
|
||||
const useStyles = makeStyles<typeof BackstageTheme>(theme => ({
|
||||
header: {
|
||||
gridArea: 'pageHeader',
|
||||
padding: theme.spacing(3),
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import { render, fireEvent } from '@testing-library/react';
|
||||
import { wrapInThemedTestApp, Keyboard } from '../../testUtils';
|
||||
import { wrapInThemedTestApp, Keyboard } from '@backstage/test-utils';
|
||||
import HeaderActionMenu from './HeaderActionMenu';
|
||||
|
||||
describe('<ComponentContextMenu />', () => {
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { wrapInThemedTestApp } from '@backstage/test-utils';
|
||||
import HeaderLabel from './HeaderLabel';
|
||||
import { wrapInThemedTestApp } from '../../testUtils';
|
||||
|
||||
describe('<HeaderLabel />', () => {
|
||||
it('should have a label', () => {
|
||||
|
||||
@@ -14,12 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Link, makeStyles, Typography } from '@material-ui/core';
|
||||
import React, { FC } from 'react';
|
||||
import { Typography, makeStyles } from '@material-ui/core';
|
||||
import { Link } from '@material-ui/core';
|
||||
import { BackstageTheme } from '../../theme/theme';
|
||||
|
||||
const useStyles = makeStyles<BackstageTheme>(theme => ({
|
||||
const useStyles = makeStyles(theme => ({
|
||||
root: {
|
||||
textAlign: 'left',
|
||||
margin: theme.spacing(2),
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { wrapInTestApp } from '../../testUtils';
|
||||
import { wrapInTestApp } from '@backstage/test-utils';
|
||||
import BottomLink from './BottomLink';
|
||||
|
||||
const minProps = {
|
||||
|
||||
@@ -14,16 +14,20 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
import { Link } from '@material-ui/core';
|
||||
import { Divider, ListItemText, makeStyles } from '@material-ui/core';
|
||||
import { ListItem, ListItemIcon } from '@material-ui/core';
|
||||
import ArrowIcon from '@material-ui/icons/ArrowForward';
|
||||
import grey from '@material-ui/core/colors/grey';
|
||||
import {
|
||||
Divider,
|
||||
Link,
|
||||
ListItem,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
makeStyles,
|
||||
} from '@material-ui/core';
|
||||
import Box from '@material-ui/core/Box';
|
||||
import { BackstageTheme } from '../../theme/theme';
|
||||
import grey from '@material-ui/core/colors/grey';
|
||||
import ArrowIcon from '@material-ui/icons/ArrowForward';
|
||||
import React, { FC } from 'react';
|
||||
|
||||
const useStyles = makeStyles<BackstageTheme>(theme => ({
|
||||
const useStyles = makeStyles(theme => ({
|
||||
root: {
|
||||
maxWidth: 'fit-content',
|
||||
padding: theme.spacing(2, 2, 2, 2.5),
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { wrapInTestApp } from '../../testUtils';
|
||||
import { wrapInTestApp } from '@backstage/test-utils';
|
||||
import InfoCard from './InfoCard';
|
||||
|
||||
const minProps = {
|
||||
|
||||
@@ -26,9 +26,8 @@ import {
|
||||
} from '@material-ui/core';
|
||||
import ErrorBoundary from '../ErrorBoundary/ErrorBoundary';
|
||||
import BottomLink, { Props as BottomLinkProps } from './BottomLink';
|
||||
import { BackstageTheme } from '../../theme/theme';
|
||||
|
||||
const useStyles = makeStyles<BackstageTheme>(theme => ({
|
||||
const useStyles = makeStyles(theme => ({
|
||||
header: {
|
||||
padding: theme.spacing(2, 2, 2, 2.5),
|
||||
},
|
||||
|
||||
Vendored
-50
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* 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 { Theme } from '@material-ui/core';
|
||||
|
||||
export type BackstageTheme = Theme & {
|
||||
palette: {
|
||||
status: {
|
||||
ok: string;
|
||||
warning: string;
|
||||
error: string;
|
||||
pending: string;
|
||||
running: string;
|
||||
background: string;
|
||||
};
|
||||
border: string;
|
||||
textVerySubtle: string;
|
||||
textSubtle: string;
|
||||
highlight: string;
|
||||
errorBackground: string;
|
||||
warningBackground: string;
|
||||
infoBackground: string;
|
||||
errorText: string;
|
||||
infoText: string;
|
||||
warningText: string;
|
||||
linkHover: string;
|
||||
link: string;
|
||||
gold: string;
|
||||
bursts: {
|
||||
fontColor: string;
|
||||
slackChannelText: string;
|
||||
backgroundColor: {
|
||||
default: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "storybook",
|
||||
"version": "0.1.1-alpha.1",
|
||||
"version": "0.1.1-alpha.2",
|
||||
"description": "Storybook build for core package",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
registry=https://registry.npmjs.org/
|
||||
@@ -0,0 +1,22 @@
|
||||
# @backstage/test-utils
|
||||
|
||||
This package provides utilities that can be used to test plugins and apps for Backstage.
|
||||
|
||||
## Installation
|
||||
|
||||
Install the package via npm or yarn:
|
||||
|
||||
```sh
|
||||
$ npm install --save-dev @backstage/test-utils
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```sh
|
||||
$ yarn add -D @backstage/test-utils
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Backstage Readme](https://github.com/spotify/backstage/blob/master/README.md)
|
||||
- [Backstage Documentation](https://github.com/spotify/backstage/blob/master/docs/README.md)
|
||||
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"name": "@backstage/test-utils",
|
||||
"description": "Utilities to test Backstage plugins and apps.",
|
||||
"version": "0.1.1-alpha.2",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"homepage": "https://backstage.io",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/spotify/backstage",
|
||||
"directory": "packages/testUtils"
|
||||
},
|
||||
"keywords": [
|
||||
"backstage"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"main": "dist/index.cjs.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "backstage-cli plugin:build",
|
||||
"lint": "backstage-cli lint",
|
||||
"test": "backstage-cli test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.2",
|
||||
"@backstage/theme": "^0.1.1-alpha.2",
|
||||
"@material-ui/core": "^4.9.1",
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.3.2",
|
||||
"@testing-library/user-event": "^7.1.2",
|
||||
"@types/jest": "^24.0.0",
|
||||
"@types/node": "^12.0.0",
|
||||
"react": "^16.12.0",
|
||||
"react-dom": "^16.12.0",
|
||||
"react-router": "^5.1.2",
|
||||
"react-router-dom": "^5.1.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@backstage/theme": "^0.1.1-alpha.2",
|
||||
"@material-ui/core": "^4.9.1",
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.3.2",
|
||||
"@testing-library/user-event": "^7.1.2",
|
||||
"react": "^16.12.0",
|
||||
"react-dom": "^16.12.0",
|
||||
"react-router": "^5.1.2",
|
||||
"react-router-dom": "^5.1.2"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright <%= YEAR %> Spotify AB
|
||||
* 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.
|
||||
@@ -13,3 +13,4 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export * from './testUtils';
|
||||
@@ -14,7 +14,4 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
...require('@spotify/web-scripts/config/jest.config.js'),
|
||||
setupFilesAfterEnv: ['../jest.setup.ts'],
|
||||
};
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
+37
-21
@@ -14,39 +14,52 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { ThemeProvider } from '@material-ui/styles';
|
||||
|
||||
import React, {
|
||||
ComponentType,
|
||||
ReactNode,
|
||||
FunctionComponent,
|
||||
ReactElement,
|
||||
} from 'react';
|
||||
import { ThemeProvider } from '@material-ui/core';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { render, RenderResult } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router';
|
||||
import { Route } from 'react-router-dom';
|
||||
|
||||
import { V1 } from '../theme/BackstageTheme';
|
||||
import ErrorBoundary from '../layout/ErrorBoundary';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { render } from '@testing-library/react';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
|
||||
export { default as Keyboard } from './Keyboard';
|
||||
export { default as mockBreakpoint } from './mockBreakpoint';
|
||||
export * from './logCollector';
|
||||
|
||||
export function wrapInTestApp(Component, initialRouterEntries) {
|
||||
const Wrapper = Component instanceof Function ? Component : () => Component;
|
||||
export function wrapInTestApp(
|
||||
Component: ComponentType | ReactNode,
|
||||
initialRouterEntries: string[] = ['/'],
|
||||
) {
|
||||
let Wrapper: ComponentType;
|
||||
if (Component instanceof Function) {
|
||||
Wrapper = Component;
|
||||
} else {
|
||||
Wrapper = (() => Component) as FunctionComponent;
|
||||
}
|
||||
|
||||
return (
|
||||
<MemoryRouter initialEntries={initialRouterEntries || ['/']}>
|
||||
<ErrorBoundary>
|
||||
<Route component={Wrapper} />
|
||||
</ErrorBoundary>
|
||||
<MemoryRouter initialEntries={initialRouterEntries}>
|
||||
<Route component={Wrapper} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
}
|
||||
|
||||
export function wrapInThemedTestApp(component, initialRouterEntries) {
|
||||
const themed = <ThemeProvider theme={V1}>{component}</ThemeProvider>;
|
||||
export function wrapInThemedTestApp(
|
||||
component: ReactNode,
|
||||
initialRouterEntries: string[] = ['/'],
|
||||
) {
|
||||
const themed = (
|
||||
<ThemeProvider theme={BackstageTheme}>{component}</ThemeProvider>
|
||||
);
|
||||
return wrapInTestApp(themed, initialRouterEntries);
|
||||
}
|
||||
|
||||
export const wrapInTheme = (component, theme = V1) => (
|
||||
export const wrapInTheme = (component: ReactNode, theme = BackstageTheme) => (
|
||||
<ThemeProvider theme={theme}>{component}</ThemeProvider>
|
||||
);
|
||||
|
||||
@@ -55,10 +68,13 @@ export const wrapInTheme = (component, theme = V1) => (
|
||||
// cleaner, since act doesn't return the result of the evaluated function.
|
||||
// https://github.com/testing-library/react-testing-library/issues/281
|
||||
// https://github.com/facebook/react/pull/14853
|
||||
export async function renderWithEffects(nodes) {
|
||||
let value;
|
||||
await act(async () => {
|
||||
value = await render(nodes);
|
||||
export async function renderWithEffects(
|
||||
nodes: ReactElement,
|
||||
): Promise<RenderResult> {
|
||||
let value: RenderResult;
|
||||
await act(() => {
|
||||
value = render(nodes);
|
||||
});
|
||||
// @ts-ignore
|
||||
return value;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
registry=https://registry.npmjs.org/
|
||||
@@ -0,0 +1,22 @@
|
||||
# @backstage/theme
|
||||
|
||||
This package provides the extended Material UI Theme(s) that power Backstage.
|
||||
|
||||
## Installation
|
||||
|
||||
Install the package via npm or yarn:
|
||||
|
||||
```sh
|
||||
$ npm install --save @backstage/theme
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```sh
|
||||
$ yarn add @backstage/theme
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Backstage Readme](https://github.com/spotify/backstage/blob/master/README.md)
|
||||
- [Backstage Documentation](https://github.com/spotify/backstage/blob/master/docs/README.md)
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "@backstage/theme",
|
||||
"description": "material-ui theme for use with Backstage.",
|
||||
"version": "0.1.1-alpha.2",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"homepage": "https://backstage.io",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/spotify/backstage",
|
||||
"directory": "packages/theme"
|
||||
},
|
||||
"keywords": [
|
||||
"backstage"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"main": "dist/index.cjs.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "backstage-cli plugin:build",
|
||||
"lint": "backstage-cli lint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.2",
|
||||
"@material-ui/core": "^4.9.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@material-ui/core": "^4.9.1"
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,8 @@ import { createMuiTheme } from '@material-ui/core';
|
||||
import { darken, lighten } from '@material-ui/core/styles/colorManipulator';
|
||||
import { blue, yellow } from '@material-ui/core/colors';
|
||||
|
||||
import { BackstageMuiTheme, BackstageMuiThemeOptions } from './types';
|
||||
|
||||
export const COLORS = {
|
||||
PAGE_BACKGROUND: '#F8F8F8',
|
||||
DEFAULT_PAGE_THEME_COLOR: '#7C3699',
|
||||
@@ -37,7 +39,7 @@ export const COLORS = {
|
||||
},
|
||||
};
|
||||
|
||||
const extendedThemeConfig = {
|
||||
const extendedThemeConfig: BackstageMuiThemeOptions = {
|
||||
props: {
|
||||
MuiGrid: {
|
||||
spacing: 2,
|
||||
@@ -49,6 +51,7 @@ const extendedThemeConfig = {
|
||||
palette: {
|
||||
background: {
|
||||
default: COLORS.PAGE_BACKGROUND,
|
||||
// @ts-ignore
|
||||
informational: '#60a3cb',
|
||||
},
|
||||
status: {
|
||||
@@ -66,6 +69,7 @@ const extendedThemeConfig = {
|
||||
default: COLORS.DEFAULT_PAGE_THEME_COLOR,
|
||||
},
|
||||
},
|
||||
// @ts-ignore
|
||||
primary: {
|
||||
main: blue[500],
|
||||
},
|
||||
@@ -115,7 +119,9 @@ const extendedThemeConfig = {
|
||||
},
|
||||
};
|
||||
|
||||
const createOverrides = theme => {
|
||||
const createOverrides = (
|
||||
theme: BackstageMuiTheme,
|
||||
): Partial<BackstageMuiTheme> => {
|
||||
return {
|
||||
overrides: {
|
||||
MuiTableRow: {
|
||||
@@ -211,7 +217,7 @@ const createOverrides = theme => {
|
||||
MuiButton: {
|
||||
text: {
|
||||
// Text buttons have less padding by default, but we want to keep the original padding
|
||||
padding: null,
|
||||
padding: undefined,
|
||||
},
|
||||
},
|
||||
MuiChip: {
|
||||
@@ -237,12 +243,15 @@ const createOverrides = theme => {
|
||||
};
|
||||
};
|
||||
|
||||
const extendedTheme = createMuiTheme(extendedThemeConfig);
|
||||
const extendedTheme = createMuiTheme(extendedThemeConfig) as BackstageMuiTheme;
|
||||
|
||||
// V1 theming
|
||||
// https://material-ui-next.com/customization/themes/
|
||||
// For CSS it is advised to use JSS, see https://material-ui-next.com/customization/css-in-js/
|
||||
const BackstageTheme = { ...extendedTheme, ...createOverrides(extendedTheme) };
|
||||
const BackstageTheme: BackstageMuiTheme = {
|
||||
...extendedTheme,
|
||||
...createOverrides(extendedTheme),
|
||||
};
|
||||
|
||||
// Temporary workaround for files incorrectly importing the theme directly
|
||||
export const V1 = BackstageTheme;
|
||||
+11
-4
@@ -18,6 +18,8 @@ import { createMuiTheme } from '@material-ui/core';
|
||||
import { darken, lighten } from '@material-ui/core/styles/colorManipulator';
|
||||
import { blue, yellow } from '@material-ui/core/colors';
|
||||
|
||||
import { BackstageMuiTheme, BackstageMuiThemeOptions } from './types';
|
||||
|
||||
export const COLORS = {
|
||||
PAGE_BACKGROUND: '#282828',
|
||||
EFAULT_PAGE_THEME_COLOR: '#232323',
|
||||
@@ -38,7 +40,7 @@ export const COLORS = {
|
||||
},
|
||||
};
|
||||
|
||||
const extendedThemeConfig = {
|
||||
const extendedThemeConfig: BackstageMuiThemeOptions = {
|
||||
props: {
|
||||
MuiGrid: {
|
||||
spacing: 2,
|
||||
@@ -50,6 +52,7 @@ const extendedThemeConfig = {
|
||||
palette: {
|
||||
background: {
|
||||
default: COLORS.PAGE_BACKGROUND,
|
||||
// @ts-ignore
|
||||
informational: '#60a3cb',
|
||||
},
|
||||
color: {
|
||||
@@ -71,6 +74,7 @@ const extendedThemeConfig = {
|
||||
default: COLORS.DEFAULT_PAGE_THEME_COLOR,
|
||||
},
|
||||
},
|
||||
// @ts-ignore
|
||||
primary: {
|
||||
main: blue[500],
|
||||
},
|
||||
@@ -120,13 +124,15 @@ const extendedThemeConfig = {
|
||||
},
|
||||
};
|
||||
|
||||
const createOverrides = theme => {
|
||||
const createOverrides = (theme: BackstageMuiTheme): BackstageMuiTheme => {
|
||||
return {
|
||||
overrides: {
|
||||
// @ts-ignore
|
||||
MuiCSSBaseline: {
|
||||
'@global': {
|
||||
body: {
|
||||
backgroundColor: theme.palette.background.default,
|
||||
// @ts-ignore
|
||||
color: theme.palette.color.default,
|
||||
},
|
||||
},
|
||||
@@ -224,7 +230,7 @@ const createOverrides = theme => {
|
||||
MuiButton: {
|
||||
text: {
|
||||
// Text buttons have less padding by default, but we want to keep the original padding
|
||||
padding: null,
|
||||
padding: undefined,
|
||||
},
|
||||
},
|
||||
MuiChip: {
|
||||
@@ -250,7 +256,7 @@ const createOverrides = theme => {
|
||||
};
|
||||
};
|
||||
|
||||
const extendedTheme = createMuiTheme(extendedThemeConfig);
|
||||
const extendedTheme = createMuiTheme(extendedThemeConfig) as BackstageMuiTheme;
|
||||
|
||||
// V1 theming
|
||||
// https://material-ui-next.com/customization/themes/
|
||||
@@ -262,4 +268,5 @@ const BackstageThemeDark = {
|
||||
|
||||
// Temporary workaround for files incorrectly importing the theme directly
|
||||
export const V1 = BackstageThemeDark;
|
||||
|
||||
export default BackstageThemeDark;
|
||||
+13
-4
@@ -18,6 +18,8 @@ import { createMuiTheme } from '@material-ui/core';
|
||||
import { darken, lighten } from '@material-ui/core/styles/colorManipulator';
|
||||
import { blue, yellow } from '@material-ui/core/colors';
|
||||
|
||||
import { BackstageMuiTheme, BackstageMuiThemeOptions } from './types';
|
||||
|
||||
export const COLORS = {
|
||||
PAGE_BACKGROUND: '#F8F8F8',
|
||||
DEFAULT_PAGE_THEME_COLOR: '#7C3699',
|
||||
@@ -37,7 +39,7 @@ export const COLORS = {
|
||||
},
|
||||
};
|
||||
|
||||
const extendedThemeConfig = {
|
||||
const extendedThemeConfig: BackstageMuiThemeOptions = {
|
||||
props: {
|
||||
MuiGrid: {
|
||||
spacing: 2,
|
||||
@@ -49,6 +51,7 @@ const extendedThemeConfig = {
|
||||
palette: {
|
||||
background: {
|
||||
default: COLORS.PAGE_BACKGROUND,
|
||||
// @ts-ignore
|
||||
informational: '#60a3cb',
|
||||
},
|
||||
color: {
|
||||
@@ -69,6 +72,7 @@ const extendedThemeConfig = {
|
||||
default: COLORS.DEFAULT_PAGE_THEME_COLOR,
|
||||
},
|
||||
},
|
||||
// @ts-ignore
|
||||
primary: {
|
||||
main: blue[500],
|
||||
},
|
||||
@@ -118,13 +122,17 @@ const extendedThemeConfig = {
|
||||
},
|
||||
};
|
||||
|
||||
const createOverrides = theme => {
|
||||
const createOverrides = (
|
||||
theme: BackstageMuiTheme,
|
||||
): Partial<BackstageMuiTheme> => {
|
||||
return {
|
||||
overrides: {
|
||||
// @ts-ignore
|
||||
MuiCSSBaseline: {
|
||||
'@global': {
|
||||
body: {
|
||||
backgroundColor: theme.palette.background.default,
|
||||
// @ts-ignore
|
||||
color: theme.palette.color.default,
|
||||
},
|
||||
},
|
||||
@@ -222,7 +230,7 @@ const createOverrides = theme => {
|
||||
MuiButton: {
|
||||
text: {
|
||||
// Text buttons have less padding by default, but we want to keep the original padding
|
||||
padding: null,
|
||||
padding: undefined,
|
||||
},
|
||||
},
|
||||
MuiChip: {
|
||||
@@ -248,7 +256,7 @@ const createOverrides = theme => {
|
||||
};
|
||||
};
|
||||
|
||||
const extendedTheme = createMuiTheme(extendedThemeConfig);
|
||||
const extendedTheme = createMuiTheme(extendedThemeConfig) as BackstageMuiTheme;
|
||||
|
||||
// V1 theming
|
||||
// https://material-ui-next.com/customization/themes/
|
||||
@@ -260,4 +268,5 @@ const BackstageThemeLight = {
|
||||
|
||||
// Temporary workaround for files incorrectly importing the theme directly
|
||||
export const V1 = BackstageThemeLight;
|
||||
|
||||
export default BackstageThemeLight;
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* 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 { default as BackstageThemeLight } from './BackstageThemeLight';
|
||||
export { default as BackstageThemeDark } from './BackstageThemeDark';
|
||||
export { default as BackstageTheme, COLORS } from './BackstageTheme';
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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 { Theme, ThemeOptions } from '@material-ui/core';
|
||||
|
||||
export type BackstageMuiPalette = Theme['palette'] & {
|
||||
status: {
|
||||
ok: string;
|
||||
warning: string;
|
||||
error: string;
|
||||
pending: string;
|
||||
running: string;
|
||||
background: string;
|
||||
};
|
||||
border: string;
|
||||
textVerySubtle: string;
|
||||
textSubtle: string;
|
||||
highlight: string;
|
||||
errorBackground: string;
|
||||
warningBackground: string;
|
||||
infoBackground: string;
|
||||
errorText: string;
|
||||
infoText: string;
|
||||
warningText: string;
|
||||
linkHover: string;
|
||||
link: string;
|
||||
gold: string;
|
||||
bursts: {
|
||||
fontColor: string;
|
||||
slackChannelText: string;
|
||||
backgroundColor: {
|
||||
default: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export interface BackstageMuiTheme extends Theme {
|
||||
palette: BackstageMuiPalette;
|
||||
}
|
||||
|
||||
export interface BackstageMuiThemeOptions extends ThemeOptions {
|
||||
palette: Partial<BackstageMuiPalette>;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user