diff --git a/.eslintrc.js b/.eslintrc.js index 969b6d7d5e..a8876a1510 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -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', + ], }; diff --git a/.github/ISSUE_TEMPLATE/ux-component_template.md b/.github/ISSUE_TEMPLATE/ux-component_template.md new file mode 100644 index 0000000000..217d0c89ee --- /dev/null +++ b/.github/ISSUE_TEMPLATE/ux-component_template.md @@ -0,0 +1,23 @@ +--- +name: 'UX Component' +about: 'For designers to request UX components to be added to the Backstage Storybook' +--- + +## ๐Ÿ—’ General + + + +## ๐Ÿ’ป Usage + + +## ๐Ÿ“ Specs + + + +## ๐Ÿ”ฎ Future + +- +- +- +- +- diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 1a57cc9083..d571703294 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -6,7 +6,6 @@ on: - '.github/workflows/cli.yml' - 'packages/cli/**' - 'packages/core/**' - types: [opened, reopened, edited, synchronize] jobs: build: diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml index 37aa16deb3..15fb2b1fc7 100644 --- a/.github/workflows/frontend.yml +++ b/.github/workflows/frontend.yml @@ -2,7 +2,6 @@ name: Frontend CI on: pull_request: - types: [opened, reopened, edited, synchronize] jobs: build: diff --git a/Dockerfile b/Dockerfile index e20a74723a..5d536de268 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 diff --git a/README.md b/README.md index f884b1881b..c012638e7d 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/docs/DLS.png b/docs/DLS.png new file mode 100644 index 0000000000..94214e4650 Binary files /dev/null and b/docs/DLS.png differ diff --git a/docs/FAQ.md b/docs/FAQ.md index 01c3a4f473..c79d9c1f60 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -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. + diff --git a/docs/README.md b/docs/README.md index c645b33024..bada62df0e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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) diff --git a/docs/design.md b/docs/design.md new file mode 100644 index 0000000000..e1a2af84b1 --- /dev/null +++ b/docs/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! + +![dls](DLS.png) + + +## โœ… 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) diff --git a/docs/getting-started/README.md b/docs/getting-started/README.md index 719e946a7e..163e52b950 100644 --- a/docs/getting-started/README.md +++ b/docs/getting-started/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) diff --git a/docs/getting-started/contributing-to-storybook.md b/docs/getting-started/contributing-to-storybook.md new file mode 100644 index 0000000000..1a7bf0f5ec --- /dev/null +++ b/docs/getting-started/contributing-to-storybook.md @@ -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` + +![](running-storybook.png) + +_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. + +![](storybook-page.png) diff --git a/docs/getting-started/create-a-plugin.md b/docs/getting-started/create-a-plugin.md index be3be26abb..7b8a7cf29b 100644 --- a/docs/getting-started/create-a-plugin.md +++ b/docs/getting-started/create-a-plugin.md @@ -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 ``` -![](create-plugin_output.png) +

+ create plugin +

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`._ -![](my-plugin_screenshot.png) +

+ my plugin +

[Next Step - Structure of a plugin](structure-of-a-plugin.md) diff --git a/docs/getting-started/create-an-app.md b/docs/getting-started/create-an-app.md new file mode 100644 index 0000000000..703d1d59a5 --- /dev/null +++ b/docs/getting-started/create-an-app.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. + +

+ create app +

+ +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) diff --git a/docs/getting-started/create-app_output.png b/docs/getting-started/create-app_output.png new file mode 100644 index 0000000000..52dcc13097 Binary files /dev/null and b/docs/getting-started/create-app_output.png differ diff --git a/docs/getting-started/running-storybook.png b/docs/getting-started/running-storybook.png new file mode 100644 index 0000000000..6cf1ded4b9 Binary files /dev/null and b/docs/getting-started/running-storybook.png differ diff --git a/docs/getting-started/storybook-page.png b/docs/getting-started/storybook-page.png new file mode 100644 index 0000000000..113f96e589 Binary files /dev/null and b/docs/getting-started/storybook-page.png differ diff --git a/lerna.json b/lerna.json index 4db7e59c00..7a9ee6a4cb 100644 --- a/lerna.json +++ b/lerna.json @@ -5,5 +5,5 @@ ], "npmClient": "yarn", "useWorkspaces": true, - "version": "0.1.1-alpha.1" + "version": "0.1.1-alpha.2" } diff --git a/package.json b/package.json index be3ee8578c..ff38d7f9ad 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/app/cypress/.eslintrc.json b/packages/app/cypress/.eslintrc.json index a467608916..2b3a458b95 100644 --- a/packages/app/cypress/.eslintrc.json +++ b/packages/app/cypress/.eslintrc.json @@ -7,6 +7,15 @@ { "assertFunctionNames": ["expect", "cy.contains"] } + ], + "import/no-extraneous-dependencies": [ + "error", + { + "devDependencies": true, + "optionalDependencies": true, + "peerDependencies": true, + "bundledDependencies": true + } ] } } diff --git a/packages/app/package.json b/packages/app/package.json index d0c8bfb123..e24ac9688c 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -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", diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 24dee65278..7f203db196 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -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 ( - + diff --git a/packages/cli/bin/backstage-cli b/packages/cli/bin/backstage-cli index b7317db25b..a8ba74f28a 100755 --- a/packages/cli/bin/backstage-cli +++ b/packages/cli/bin/backstage-cli @@ -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(); } diff --git a/packages/cli/config/eslint.js b/packages/cli/config/eslint.js index 32d1e220f7..dbdb349274 100644 --- a/packages/cli/config/eslint.js +++ b/packages/cli/config/eslint.js @@ -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, + }, + ], + }, + }, ], }; diff --git a/packages/cli/config/jest.js b/packages/cli/config/jest.js index e443ceec1b..055c3e73c5 100644 --- a/packages/cli/config/jest.js +++ b/packages/cli/config/jest.js @@ -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: [''], + }; // Use src/setupTests.ts as the default location for configuring test env if (fs.existsSync('src/setupTests.ts')) { diff --git a/packages/cli/config/tsconfig.json b/packages/cli/config/tsconfig.json new file mode 100644 index 0000000000..abb10087ca --- /dev/null +++ b/packages/cli/config/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "@spotify/web-scripts/config/tsconfig.json", + "exclude": ["**/*.test.*"], + "compilerOptions": { + "allowJs": true, + "noEmit": false, + "declarationMap": true, + "incremental": true, + "module": "es6" + } +} diff --git a/packages/cli/package.json b/packages/cli/package.json index 718867266f..40b5d78040 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -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", diff --git a/packages/cli/src/commands/app/build.ts b/packages/cli/src/commands/app/build.ts index 5dc623831e..d5a01d13a7 100644 --- a/packages/cli/src/commands/app/build.ts +++ b/packages/cli/src/commands/app/build.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { run } from '../../helpers/run'; +import { run } from 'helpers/run'; export default async () => { const args = ['build']; diff --git a/packages/cli/src/commands/app/serve.ts b/packages/cli/src/commands/app/serve.ts index 21241c99e3..51806aa9d6 100644 --- a/packages/cli/src/commands/app/serve.ts +++ b/packages/cli/src/commands/app/serve.ts @@ -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); }; diff --git a/packages/cli/src/commands/build-cache/cache.ts b/packages/cli/src/commands/build-cache/cache.ts index 464071c95f..4dddaedc6f 100644 --- a/packages/cli/src/commands/build-cache/cache.ts +++ b/packages/cli/src/commands/build-cache/cache.ts @@ -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 { - 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( diff --git a/packages/cli/src/commands/build-cache/index.ts b/packages/cli/src/commands/build-cache/index.ts index ab8984ecc3..a8a1a27e9d 100644 --- a/packages/cli/src/commands/build-cache/index.ts +++ b/packages/cli/src/commands/build-cache/index.ts @@ -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, +): Promise { 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'; diff --git a/packages/cli/src/commands/build-cache/options.ts b/packages/cli/src/commands/build-cache/options.ts index ae5404bb9f..6903c01ddf 100644 --- a/packages/cli/src/commands/build-cache/options.ts +++ b/packages/cli/src/commands/build-cache/options.ts @@ -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 = '/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 { - const argTransformer = (arg: string) => - resolvePath(arg.replace(//g, paths.targetRoot).replace(/'/g, '')); +function transformPath(path: string): string { + return resolvePath( + path.replace(//g, paths.targetRoot).replace(/'/g, ''), + ); +} - const inputs = cmd.input.map(argTransformer) as string[]; +export async function parseOptions(cmd: Command): Promise { + 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, + }; +} diff --git a/packages/cli/src/commands/create-app/createApp.ts b/packages/cli/src/commands/create-app/createApp.ts index 03f571fed5..826f9866d2 100644 --- a/packages/cli/src/commands/create-app/createApp.ts +++ b/packages/cli/src/commands/create-app/createApp.ts @@ -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...'); diff --git a/packages/cli/src/commands/create-plugin/createPlugin.ts b/packages/cli/src/commands/create-plugin/createPlugin.ts index 027a53f2b4..7efb29d270 100644 --- a/packages/cli/src/commands/create-plugin/createPlugin.ts +++ b/packages/cli/src/commands/create-plugin/createPlugin.ts @@ -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(); diff --git a/packages/cli/src/commands/lint.ts b/packages/cli/src/commands/lint.ts index 3fbc30f897..d1b5a361a4 100644 --- a/packages/cli/src/commands/lint.ts +++ b/packages/cli/src/commands/lint.ts @@ -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']; diff --git a/packages/cli/src/commands/plugin/build.ts b/packages/cli/src/commands/plugin/build.ts index cb28d4cf5f..aa9c68c494 100644 --- a/packages/cli/src/commands/plugin/build.ts +++ b/packages/cli/src/commands/plugin/build.ts @@ -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); }; diff --git a/packages/cli/src/commands/plugin/rollup.config.ts b/packages/cli/src/commands/plugin/rollup.config.ts index fc4b5e92ef..bc73a789ff 100644 --- a/packages/cli/src/commands/plugin/rollup.config.ts +++ b/packages/cli/src/commands/plugin/rollup.config.ts @@ -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; diff --git a/packages/cli/src/commands/plugin/testCommand.ts b/packages/cli/src/commands/plugin/testCommand.ts index 0bc35e13c1..a1c4572422 100644 --- a/packages/cli/src/commands/plugin/testCommand.ts +++ b/packages/cli/src/commands/plugin/testCommand.ts @@ -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']; diff --git a/packages/cli/src/commands/testCommand.ts b/packages/cli/src/commands/testCommand.ts index 3fc87b158b..10a4fb54f3 100644 --- a/packages/cli/src/commands/testCommand.ts +++ b/packages/cli/src/commands/testCommand.ts @@ -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')]; diff --git a/packages/cli/src/commands/watch-deps/index.ts b/packages/cli/src/commands/watch-deps/index.ts index 7ffa67ed7a..11093d7387 100644 --- a/packages/cli/src/commands/watch-deps/index.ts +++ b/packages/cli/src/commands/watch-deps/index.ts @@ -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); }); } diff --git a/packages/cli/src/commands/watch-deps/packages.ts b/packages/cli/src/commands/watch-deps/packages.ts index e80de3651d..f5744a41d4 100644 --- a/packages/cli/src/commands/watch-deps/packages.ts +++ b/packages/cli/src/commands/watch-deps/packages.ts @@ -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); -} diff --git a/packages/cli/src/helpers/paths.ts b/packages/cli/src/helpers/paths.ts index f49bfa9689..746b3f6ea8 100644 --- a/packages/cli/src/helpers/paths.ts +++ b/packages/cli/src/helpers/paths.ts @@ -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 diff --git a/packages/cli/src/helpers/version.ts b/packages/cli/src/helpers/version.ts new file mode 100644 index 0000000000..24734b87cc --- /dev/null +++ b/packages/cli/src/helpers/version.ts @@ -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')); diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 071f846803..531561acce 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -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', '/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( - actionFunc: (...args: T) => Promise, + actionRequireFunc: () => { default(...args: T): Promise }, ): (...args: T) => Promise { return async (...args: T) => { try { + const actionFunc = actionRequireFunc().default; await actionFunc(...args); process.exit(0); } catch (error) { diff --git a/packages/cli/templates/default-app/packages/app/src/App.tsx b/packages/cli/templates/default-app/packages/app/src/App.tsx index 14a07870b2..ec8d8d435a 100644 --- a/packages/cli/templates/default-app/packages/app/src/App.tsx +++ b/packages/cli/templates/default-app/packages/app/src/App.tsx @@ -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'; diff --git a/packages/cli/templates/default-app/plugins/welcome/package.json.hbs b/packages/cli/templates/default-app/plugins/welcome/package.json.hbs index 1aa7f2c5c7..9069ce6f8e 100644 --- a/packages/cli/templates/default-app/plugins/welcome/package.json.hbs +++ b/packages/cli/templates/default-app/plugins/welcome/package.json.hbs @@ -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" } } diff --git a/packages/cli/templates/default-app/plugins/welcome/src/components/WelcomePage/WelcomePage.test.tsx b/packages/cli/templates/default-app/plugins/welcome/src/components/WelcomePage/WelcomePage.test.tsx index 4b9b12913d..6d9268fadd 100644 --- a/packages/cli/templates/default-app/plugins/welcome/src/components/WelcomePage/WelcomePage.test.tsx +++ b/packages/cli/templates/default-app/plugins/welcome/src/components/WelcomePage/WelcomePage.test.tsx @@ -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', () => { diff --git a/packages/cli/templates/default-app/plugins/welcome/src/components/WelcomePage/WelcomePage.tsx b/packages/cli/templates/default-app/plugins/welcome/src/components/WelcomePage/WelcomePage.tsx index 19f9ee55e2..d9ef0524c6 100644 --- a/packages/cli/templates/default-app/plugins/welcome/src/components/WelcomePage/WelcomePage.tsx +++ b/packages/cli/templates/default-app/plugins/welcome/src/components/WelcomePage/WelcomePage.tsx @@ -36,7 +36,7 @@ const WelcomePage: FC<{}> = () => { - + You now have a running instance of Backstage! diff --git a/packages/cli/templates/default-app/tsconfig.json b/packages/cli/templates/default-app/tsconfig.json index 25f61cc933..f278b2777d 100644 --- a/packages/cli/templates/default-app/tsconfig.json +++ b/packages/cli/templates/default-app/tsconfig.json @@ -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" } diff --git a/packages/cli/templates/default-plugin/package.json.hbs b/packages/cli/templates/default-plugin/package.json.hbs index f48b37fbc7..49d8d20d71 100644 --- a/packages/cli/templates/default-plugin/package.json.hbs +++ b/packages/cli/templates/default-plugin/package.json.hbs @@ -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" } } diff --git a/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.test.tsx.hbs b/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.test.tsx.hbs index 15db0863d2..80b97169bf 100644 --- a/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.test.tsx.hbs +++ b/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.test.tsx.hbs @@ -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', () => { diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index 84b9319b94..8c7fb90d47 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -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" } } diff --git a/packages/core/package.json b/packages/core/package.json index dbac96cf02..85054eba14 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -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" } } diff --git a/packages/core/src/api/apis/ApiProvider.test.tsx b/packages/core/src/api/apis/ApiProvider.test.tsx index 20634f74af..5734899f68 100644 --- a/packages/core/src/api/apis/ApiProvider.test.tsx +++ b/packages/core/src/api/apis/ApiProvider.test.tsx @@ -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; diff --git a/packages/core/src/components/CircleProgress.test.js b/packages/core/src/components/CircleProgress.test.js index 7d4b294dc1..68e8f027c2 100644 --- a/packages/core/src/components/CircleProgress.test.js +++ b/packages/core/src/components/CircleProgress.test.js @@ -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('', () => { it('renders without exploding', () => { diff --git a/packages/core/src/components/CircleProgress.tsx b/packages/core/src/components/CircleProgress.tsx index 6d30916c81..d5cd191b3d 100644 --- a/packages/core/src/components/CircleProgress.tsx +++ b/packages/core/src/components/CircleProgress.tsx @@ -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(theme => ({ root: { position: 'relative', lineHeight: 0, diff --git a/packages/core/src/components/HorizontalScrollGrid.stories.tsx b/packages/core/src/components/HorizontalScrollGrid.stories.tsx new file mode 100644 index 0000000000..7ab3efd524 --- /dev/null +++ b/packages/core/src/components/HorizontalScrollGrid.stories.tsx @@ -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 = () => ( +
+ + {opacityArray.map(element => { + const style = { backgroundColor: `rgba(0, 185, 151, ${element})` }; + return
; + })} + +
+); diff --git a/packages/core/src/components/HorizontalScrollGrid.test.js b/packages/core/src/components/HorizontalScrollGrid.test.js index 6838b2bfc7..d7f1e2387a 100644 --- a/packages/core/src/components/HorizontalScrollGrid.test.js +++ b/packages/core/src/components/HorizontalScrollGrid.test.js @@ -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'; diff --git a/packages/core/src/components/Lifecycle/LifecycleAlpha.stories.tsx b/packages/core/src/components/Lifecycle/LifecycleAlpha.stories.tsx new file mode 100644 index 0000000000..1b044fcd5f --- /dev/null +++ b/packages/core/src/components/Lifecycle/LifecycleAlpha.stories.tsx @@ -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 + +); +export const Shorthand = () => ( + <> + This feature is in + +); diff --git a/packages/core/src/components/Lifecycle/LifecycleBeta.stories.tsx b/packages/core/src/components/Lifecycle/LifecycleBeta.stories.tsx new file mode 100644 index 0000000000..b74171b5ea --- /dev/null +++ b/packages/core/src/components/Lifecycle/LifecycleBeta.stories.tsx @@ -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 + +); +export const Shorthand = () => ( + <> + This feature is in + +); diff --git a/packages/core/src/components/ProgressCard.stories.tsx b/packages/core/src/components/ProgressCard.stories.tsx new file mode 100644 index 0000000000..9b99b5d621 --- /dev/null +++ b/packages/core/src/components/ProgressCard.stories.tsx @@ -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 = () => ( + + + + + + + + + + + +); + +export const Subhead = () => ( + + + + + + + + + + + +); + +export const LinkInFooter = () => ( + + + + + + + + + + + +); diff --git a/packages/core/src/components/ProgressCard.test.js b/packages/core/src/components/ProgressCard.test.js index 4ebb869285..7357cab812 100644 --- a/packages/core/src/components/ProgressCard.test.js +++ b/packages/core/src/components/ProgressCard.test.js @@ -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'; diff --git a/packages/core/src/components/ProgressCard.tsx b/packages/core/src/components/ProgressCard.tsx index 6a3b87ba89..ef84c40499 100644 --- a/packages/core/src/components/ProgressCard.tsx +++ b/packages/core/src/components/ProgressCard.tsx @@ -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 => { 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 (
- +
); diff --git a/packages/core/src/components/Status/Status.tsx b/packages/core/src/components/Status/Status.tsx index f6f331edce..d03dc52b6e 100644 --- a/packages/core/src/components/Status/Status.tsx +++ b/packages/core/src/components/Status/Status.tsx @@ -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(theme => ({ status: { width: 12, height: 12, diff --git a/packages/core/src/icons/icons.tsx b/packages/core/src/icons/icons.tsx index a588a4a2a1..454a10c264 100644 --- a/packages/core/src/icons/icons.tsx +++ b/packages/core/src/icons/icons.tsx @@ -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 = { diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 33054ae81c..4472f57d40 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -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'; diff --git a/packages/core/src/layout/ContentHeader/ContentHeader.test.tsx b/packages/core/src/layout/ContentHeader/ContentHeader.test.tsx index 50b4f921ac..1ab171bcf1 100644 --- a/packages/core/src/layout/ContentHeader/ContentHeader.test.tsx +++ b/packages/core/src/layout/ContentHeader/ContentHeader.test.tsx @@ -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 }) =>
defaultTitle: {defaultTitle}
; diff --git a/packages/core/src/layout/ContentHeader/ContentHeader.tsx b/packages/core/src/layout/ContentHeader/ContentHeader.tsx index 0f83f54c25..61bea92639 100644 --- a/packages/core/src/layout/ContentHeader/ContentHeader.tsx +++ b/packages/core/src/layout/ContentHeader/ContentHeader.tsx @@ -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(theme => ({ +const useStyles = makeStyles(theme => ({ container: { width: '100%', display: 'flex', diff --git a/packages/core/src/layout/ErrorBoundary/ErrorBoundary.tsx b/packages/core/src/layout/ErrorBoundary/ErrorBoundary.tsx index 34a903480e..9e9d639548 100644 --- a/packages/core/src/layout/ErrorBoundary/ErrorBoundary.tsx +++ b/packages/core/src/layout/ErrorBoundary/ErrorBoundary.tsx @@ -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 = ({ slackChannel }) => { return (
- Something went wrong here. Please contact {slackChannel} for help. + Something went wrong here.{' '} + {slackChannel && <>Please contact {slackChannel} for help.}
); }; diff --git a/packages/core/src/layout/Header/Header.test.tsx b/packages/core/src/layout/Header/Header.test.tsx index 5213fe1ac3..79d74791c8 100644 --- a/packages/core/src/layout/Header/Header.test.tsx +++ b/packages/core/src/layout/Header/Header.test.tsx @@ -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 }) =>
defaultTitle: {defaultTitle}
; diff --git a/packages/core/src/layout/Header/Header.tsx b/packages/core/src/layout/Header/Header.tsx index 5b3bb7ee60..6af4019d13 100644 --- a/packages/core/src/layout/Header/Header.tsx +++ b/packages/core/src/layout/Header/Header.tsx @@ -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(theme => ({ +const useStyles = makeStyles(theme => ({ header: { gridArea: 'pageHeader', padding: theme.spacing(3), diff --git a/packages/core/src/layout/HeaderActionMenu/HeaderActionMenu.test.tsx b/packages/core/src/layout/HeaderActionMenu/HeaderActionMenu.test.tsx index 3a32d9371e..17822228e8 100644 --- a/packages/core/src/layout/HeaderActionMenu/HeaderActionMenu.test.tsx +++ b/packages/core/src/layout/HeaderActionMenu/HeaderActionMenu.test.tsx @@ -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('', () => { diff --git a/packages/core/src/layout/HeaderLabel/HeaderLabel.test.tsx b/packages/core/src/layout/HeaderLabel/HeaderLabel.test.tsx index 727c890e35..cbdfb3da80 100644 --- a/packages/core/src/layout/HeaderLabel/HeaderLabel.test.tsx +++ b/packages/core/src/layout/HeaderLabel/HeaderLabel.test.tsx @@ -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('', () => { it('should have a label', () => { diff --git a/packages/core/src/layout/HeaderLabel/HeaderLabel.tsx b/packages/core/src/layout/HeaderLabel/HeaderLabel.tsx index 4349399194..e2fd11ff15 100644 --- a/packages/core/src/layout/HeaderLabel/HeaderLabel.tsx +++ b/packages/core/src/layout/HeaderLabel/HeaderLabel.tsx @@ -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(theme => ({ +const useStyles = makeStyles(theme => ({ root: { textAlign: 'left', margin: theme.spacing(2), diff --git a/packages/core/src/layout/InfoCard/BottomLink.test.tsx b/packages/core/src/layout/InfoCard/BottomLink.test.tsx index d240a232b7..f301de1c84 100644 --- a/packages/core/src/layout/InfoCard/BottomLink.test.tsx +++ b/packages/core/src/layout/InfoCard/BottomLink.test.tsx @@ -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 = { diff --git a/packages/core/src/layout/InfoCard/BottomLink.tsx b/packages/core/src/layout/InfoCard/BottomLink.tsx index bf82f6cd11..6721eda0f1 100644 --- a/packages/core/src/layout/InfoCard/BottomLink.tsx +++ b/packages/core/src/layout/InfoCard/BottomLink.tsx @@ -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(theme => ({ +const useStyles = makeStyles(theme => ({ root: { maxWidth: 'fit-content', padding: theme.spacing(2, 2, 2, 2.5), diff --git a/packages/core/src/layout/InfoCard/InfoCard.test.tsx b/packages/core/src/layout/InfoCard/InfoCard.test.tsx index 7d184d2f89..4d8221d5b8 100644 --- a/packages/core/src/layout/InfoCard/InfoCard.test.tsx +++ b/packages/core/src/layout/InfoCard/InfoCard.test.tsx @@ -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 = { diff --git a/packages/core/src/layout/InfoCard/InfoCard.tsx b/packages/core/src/layout/InfoCard/InfoCard.tsx index 5b3ab1cfd1..10f9f60da0 100644 --- a/packages/core/src/layout/InfoCard/InfoCard.tsx +++ b/packages/core/src/layout/InfoCard/InfoCard.tsx @@ -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(theme => ({ +const useStyles = makeStyles(theme => ({ header: { padding: theme.spacing(2, 2, 2, 2.5), }, diff --git a/packages/core/jest.setup.ts b/packages/core/src/setupTests.ts similarity index 100% rename from packages/core/jest.setup.ts rename to packages/core/src/setupTests.ts diff --git a/packages/core/src/theme/theme.d.ts b/packages/core/src/theme/theme.d.ts deleted file mode 100644 index 7c9f552310..0000000000 --- a/packages/core/src/theme/theme.d.ts +++ /dev/null @@ -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; - }; - }; - }; -}; diff --git a/packages/storybook/package.json b/packages/storybook/package.json index 056c80b963..fc15da6207 100644 --- a/packages/storybook/package.json +++ b/packages/storybook/package.json @@ -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": { diff --git a/packages/test-utils/.npmrc b/packages/test-utils/.npmrc new file mode 100644 index 0000000000..214c29d139 --- /dev/null +++ b/packages/test-utils/.npmrc @@ -0,0 +1 @@ +registry=https://registry.npmjs.org/ diff --git a/packages/test-utils/README.md b/packages/test-utils/README.md new file mode 100644 index 0000000000..46a2e2dab3 --- /dev/null +++ b/packages/test-utils/README.md @@ -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) diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json new file mode 100644 index 0000000000..ce2861353f --- /dev/null +++ b/packages/test-utils/package.json @@ -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" + } +} diff --git a/scripts/copyright.js b/packages/test-utils/src/index.ts similarity index 90% rename from scripts/copyright.js rename to packages/test-utils/src/index.ts index 4376d55847..43faf28a4a 100644 --- a/scripts/copyright.js +++ b/packages/test-utils/src/index.ts @@ -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'; diff --git a/packages/core/jest.config.ts b/packages/test-utils/src/setupTests.ts similarity index 82% rename from packages/core/jest.config.ts rename to packages/test-utils/src/setupTests.ts index 2e584f12dc..8925258421 100644 --- a/packages/core/jest.config.ts +++ b/packages/test-utils/src/setupTests.ts @@ -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'; diff --git a/packages/core/src/testUtils/Keyboard.js b/packages/test-utils/src/testUtils/Keyboard.js similarity index 100% rename from packages/core/src/testUtils/Keyboard.js rename to packages/test-utils/src/testUtils/Keyboard.js diff --git a/packages/core/src/testUtils/Keyboard.test.js b/packages/test-utils/src/testUtils/Keyboard.test.js similarity index 100% rename from packages/core/src/testUtils/Keyboard.test.js rename to packages/test-utils/src/testUtils/Keyboard.test.js diff --git a/packages/core/src/testUtils/index.js b/packages/test-utils/src/testUtils/index.tsx similarity index 57% rename from packages/core/src/testUtils/index.js rename to packages/test-utils/src/testUtils/index.tsx index a0eab3e72b..d9a4389ac8 100644 --- a/packages/core/src/testUtils/index.js +++ b/packages/test-utils/src/testUtils/index.tsx @@ -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 ( - - - - + + ); } -export function wrapInThemedTestApp(component, initialRouterEntries) { - const themed = {component}; +export function wrapInThemedTestApp( + component: ReactNode, + initialRouterEntries: string[] = ['/'], +) { + const themed = ( + {component} + ); return wrapInTestApp(themed, initialRouterEntries); } -export const wrapInTheme = (component, theme = V1) => ( +export const wrapInTheme = (component: ReactNode, theme = BackstageTheme) => ( {component} ); @@ -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 { + let value: RenderResult; + await act(() => { + value = render(nodes); }); + // @ts-ignore return value; } diff --git a/packages/core/src/testUtils/logCollector.js b/packages/test-utils/src/testUtils/logCollector.js similarity index 100% rename from packages/core/src/testUtils/logCollector.js rename to packages/test-utils/src/testUtils/logCollector.js diff --git a/packages/core/src/testUtils/mockBreakpoint.ts b/packages/test-utils/src/testUtils/mockBreakpoint.ts similarity index 100% rename from packages/core/src/testUtils/mockBreakpoint.ts rename to packages/test-utils/src/testUtils/mockBreakpoint.ts diff --git a/packages/test-utils/tsconfig.json b/packages/test-utils/tsconfig.json new file mode 100644 index 0000000000..596e2cf729 --- /dev/null +++ b/packages/test-utils/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src"] +} diff --git a/packages/theme/.npmrc b/packages/theme/.npmrc new file mode 100644 index 0000000000..214c29d139 --- /dev/null +++ b/packages/theme/.npmrc @@ -0,0 +1 @@ +registry=https://registry.npmjs.org/ diff --git a/packages/theme/README.md b/packages/theme/README.md new file mode 100644 index 0000000000..a1b6f81f92 --- /dev/null +++ b/packages/theme/README.md @@ -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) diff --git a/packages/theme/package.json b/packages/theme/package.json new file mode 100644 index 0000000000..450f8f6f86 --- /dev/null +++ b/packages/theme/package.json @@ -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" + } +} diff --git a/packages/core/src/theme/BackstageTheme.js b/packages/theme/src/BackstageTheme.ts similarity index 93% rename from packages/core/src/theme/BackstageTheme.js rename to packages/theme/src/BackstageTheme.ts index de4bbffca5..202bdbc08c 100644 --- a/packages/core/src/theme/BackstageTheme.js +++ b/packages/theme/src/BackstageTheme.ts @@ -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 => { 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; diff --git a/packages/core/src/theme/BackstageThemeDark.js b/packages/theme/src/BackstageThemeDark.ts similarity index 94% rename from packages/core/src/theme/BackstageThemeDark.js rename to packages/theme/src/BackstageThemeDark.ts index b5562a50ba..b8b89c8f0a 100644 --- a/packages/core/src/theme/BackstageThemeDark.js +++ b/packages/theme/src/BackstageThemeDark.ts @@ -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; diff --git a/packages/core/src/theme/BackstageThemeLight.js b/packages/theme/src/BackstageThemeLight.ts similarity index 94% rename from packages/core/src/theme/BackstageThemeLight.js rename to packages/theme/src/BackstageThemeLight.ts index 4f44a0c2b2..14bc942910 100644 --- a/packages/core/src/theme/BackstageThemeLight.js +++ b/packages/theme/src/BackstageThemeLight.ts @@ -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 => { 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; diff --git a/packages/theme/src/index.ts b/packages/theme/src/index.ts new file mode 100644 index 0000000000..136355dc37 --- /dev/null +++ b/packages/theme/src/index.ts @@ -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'; diff --git a/packages/theme/src/types.ts b/packages/theme/src/types.ts new file mode 100644 index 0000000000..9a3e93c3a9 --- /dev/null +++ b/packages/theme/src/types.ts @@ -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; +} diff --git a/packages/theme/tsconfig.json b/packages/theme/tsconfig.json new file mode 100644 index 0000000000..596e2cf729 --- /dev/null +++ b/packages/theme/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src"] +} diff --git a/plugins/home-page/package.json b/plugins/home-page/package.json index 88afe98bd4..73e208b737 100644 --- a/plugins/home-page/package.json +++ b/plugins/home-page/package.json @@ -1,19 +1,19 @@ { "name": "@backstage/plugin-home-page", - "version": "0.1.1-alpha.1", + "version": "0.1.1-alpha.2", "main": "dist/index.cjs.js", "types": "dist/index.d.ts", "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" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.1", - "@backstage/core": "^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", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@testing-library/jest-dom": "^4.2.4", @@ -26,7 +26,8 @@ "react-dom": "^16.12.0" }, "peerDependencies": { - "@backstage/core": "^0.1.1-alpha.1", + "@backstage/core": "^0.1.1-alpha.2", + "@backstage/theme": "^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", diff --git a/plugins/home-page/src/components/HomePage/HomePage.test.tsx b/plugins/home-page/src/components/HomePage/HomePage.test.tsx index 6b2c1bfd11..713f0afe1e 100644 --- a/plugins/home-page/src/components/HomePage/HomePage.test.tsx +++ b/plugins/home-page/src/components/HomePage/HomePage.test.tsx @@ -18,7 +18,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import HomePage from './HomePage'; import { ThemeProvider } from '@material-ui/core'; -import { BackstageTheme } from '@backstage/core'; +import { BackstageTheme } from '@backstage/theme'; describe('HomePage', () => { it('should render', () => { diff --git a/plugins/welcome/package.json b/plugins/welcome/package.json index 8eb9f45f7f..f4f33d6265 100644 --- a/plugins/welcome/package.json +++ b/plugins/welcome/package.json @@ -1,19 +1,19 @@ { "name": "@backstage/plugin-welcome", - "version": "0.1.1-alpha.1", + "version": "0.1.1-alpha.2", "main": "dist/index.cjs.js", "types": "dist/index.d.ts", "private": true, "license": "Apache-2.0", "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" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.1", - "@backstage/core": "^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", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", @@ -22,7 +22,8 @@ "@types/testing-library__jest-dom": "5.0.2" }, "peerDependencies": { - "@backstage/core": "^0.1.1-alpha.1", + "@backstage/core": "^0.1.1-alpha.2", + "@backstage/theme": "^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", diff --git a/plugins/welcome/src/components/WelcomePage/WelcomePage.test.tsx b/plugins/welcome/src/components/WelcomePage/WelcomePage.test.tsx index b4139e4ed3..9b78726f0a 100644 --- a/plugins/welcome/src/components/WelcomePage/WelcomePage.test.tsx +++ b/plugins/welcome/src/components/WelcomePage/WelcomePage.test.tsx @@ -18,12 +18,8 @@ import React from 'react'; import { render } from '@testing-library/react'; import WelcomePage from './WelcomePage'; import { ThemeProvider } from '@material-ui/core'; -import { - BackstageTheme, - ApiProvider, - ApiRegistry, - errorApiRef, -} from '@backstage/core'; +import { BackstageTheme } from '@backstage/theme'; +import { ApiProvider, ApiRegistry, errorApiRef } from '@backstage/core'; describe('WelcomePage', () => { it('should render', () => { diff --git a/scripts/cli-e2e-test.js b/scripts/cli-e2e-test.js index c1e90ea0ce..48058550d9 100644 --- a/scripts/cli-e2e-test.js +++ b/scripts/cli-e2e-test.js @@ -19,7 +19,7 @@ const childProcess = require('child_process'); const { spawn } = childProcess; const Browser = require('zombie'); -const EXPECTED_LOAD_ERRORS = /connect ECONNREFUSED|did not get to load all resources/; +const EXPECTED_LOAD_ERRORS = /ECONNREFUSED|ECONNRESET|did not get to load all resources/; Browser.localhost('localhost', 3000); diff --git a/tsconfig.json b/tsconfig.json index abb10087ca..f278b2777d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,3 @@ { - "extends": "@spotify/web-scripts/config/tsconfig.json", - "exclude": ["**/*.test.*"], - "compilerOptions": { - "allowJs": true, - "noEmit": false, - "declarationMap": true, - "incremental": true, - "module": "es6" - } + "extends": "@backstage/cli/config/tsconfig.json" } diff --git a/yarn.lock b/yarn.lock index 78d92bfda5..1f7b93d669 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2783,6 +2783,13 @@ resolved "https://registry.npmjs.org/@spotify/eslint-config-base/-/eslint-config-base-6.1.0.tgz#b48d2764049d56a7d83a95709ec1e6733cb19f2a" integrity sha512-oxR3OBBrms09Ih6yPrqcaeKgwchdw9XgRvKsF6x6uPiMy0OVYald/+GkBtyQy+IChD14WStV/XosqNxEZeTJ4Q== +"@spotify/eslint-config-oss@^1.0.1": + version "1.0.1" + resolved "https://registry.npmjs.org/@spotify/eslint-config-oss/-/eslint-config-oss-1.0.1.tgz#01a95517e05063487c8e06b0b95cbdda33014a2a" + integrity sha512-G6eVEsp5WnvCglI9uhzhSeUnsKdpJ5gOLyhdZBEtqt8+MdpQSDECIfnkI8mV06dQ8FKtbqVd2NiMbkCC/gwrFQ== + dependencies: + eslint-plugin-notice "^0.8.9" + "@spotify/eslint-config-react@^6.0.0": version "6.0.0" resolved "https://registry.npmjs.org/@spotify/eslint-config-react/-/eslint-config-react-6.0.0.tgz#9cfc88362c7ffcd14e4dde73c196d1bc8583051d" @@ -3650,6 +3657,11 @@ resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339" integrity sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA== +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= + "@types/mime@*": version "2.0.1" resolved "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d" @@ -5823,6 +5835,14 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz#6e98081ed2d17faab615eb52ac66ec1fe6209e72" + integrity sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + change-emitter@^0.1.2: version "0.1.6" resolved "https://registry.npmjs.org/change-emitter/-/change-emitter-0.1.6.tgz#e8b2fe3d7f1ab7d69a32199aff91ea6931409515" @@ -6095,7 +6115,7 @@ clone@^1.0.2: resolved "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= -clsx@^1.0.2, clsx@^1.0.4: +clsx@^1.0.2, clsx@^1.0.4, clsx@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/clsx/-/clsx-1.1.0.tgz#62937c6adfea771247c34b54d320fb99624f5702" integrity sha512-3avwM37fSK5oP6M5rQ9CNe99lwxhXDOeSWVPAOYF6OazUTgZCMb0yWlJpmdD74REy1gkEaFiub2ULv4fq9GUhA== @@ -6676,14 +6696,6 @@ cross-env@^7.0.0: dependencies: cross-spawn "^7.0.1" -cross-fetch@^3.0.4: - version "3.0.4" - resolved "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.4.tgz#7bef7020207e684a7638ef5f2f698e24d9eb283c" - integrity sha512-MSHgpjQqgbT/94D4CyADeNoYh52zMkCX4pcJvPP5WqPsLFMKjr2TCMg381ox5qI0ii2dPwaLx/00477knXqXVw== - dependencies: - node-fetch "2.6.0" - whatwg-fetch "3.0.0" - cross-spawn-promise@^0.10.1: version "0.10.2" resolved "https://registry.npmjs.org/cross-spawn-promise/-/cross-spawn-promise-0.10.2.tgz#0e6338149caf53a6d557ac5c65efb3086d8704ac" @@ -7434,7 +7446,7 @@ dir-glob@2.0.0: arrify "^1.0.1" path-type "^3.0.0" -dir-glob@^2.2.2: +dir-glob@^2.0.0, dir-glob@^2.2.2: version "2.2.2" resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz#fa09f0694153c8918b18ba0deafae94769fc50c4" integrity sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw== @@ -7979,6 +7991,14 @@ eslint-loader@3.0.3: object-hash "^2.0.1" schema-utils "^2.6.1" +eslint-module-utils@^2.1.1: + version "2.6.0" + resolved "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz#579ebd094f56af7797d19c9866c9c9486629bfa6" + integrity sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA== + dependencies: + debug "^2.6.9" + pkg-dir "^2.0.0" + eslint-module-utils@^2.4.1: version "2.5.2" resolved "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.5.2.tgz#7878f7504824e1b857dd2505b59a8e5eda26a708" @@ -8019,6 +8039,24 @@ eslint-plugin-import@2.20.1: read-pkg-up "^2.0.0" resolve "^1.12.0" +eslint-plugin-import@^2.20.2: + version "2.20.2" + resolved "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.20.2.tgz#91fc3807ce08be4837141272c8b99073906e588d" + integrity sha512-FObidqpXrR8OnCh4iNsxy+WACztJLXAHBO5hK79T1Hc77PgQZkyDGA5Ag9xAvRpglvLNxhH/zSmZ70/pZ31dHg== + dependencies: + array-includes "^3.0.3" + array.prototype.flat "^1.2.1" + contains-path "^0.1.0" + debug "^2.6.9" + doctrine "1.5.0" + eslint-import-resolver-node "^0.3.2" + eslint-module-utils "^2.4.1" + has "^1.0.3" + minimatch "^3.0.4" + object.values "^1.1.0" + read-pkg-up "^2.0.0" + resolve "^1.12.0" + eslint-plugin-jest@^23.6.0: version "23.8.2" resolved "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-23.8.2.tgz#6f28b41c67ef635f803ebd9e168f6b73858eb8d4" @@ -8041,6 +8079,19 @@ eslint-plugin-jsx-a11y@6.2.3, eslint-plugin-jsx-a11y@^6.2.1: has "^1.0.3" jsx-ast-utils "^2.2.1" +eslint-plugin-monorepo@^0.2.1: + version "0.2.1" + resolved "https://registry.npmjs.org/eslint-plugin-monorepo/-/eslint-plugin-monorepo-0.2.1.tgz#96cfc4af241077675f40d7017377897fb8ea537b" + integrity sha512-82JaAjuajVAsDT+pMvdt275H6F55H3MEofaMZbJurGqfXpPDT4eayTgYyyjfd1XR8VD1S+ORbuHCULnSqNyD9g== + dependencies: + eslint-module-utils "^2.1.1" + get-monorepo-packages "^1.1.0" + globby "^7.1.1" + load-json-file "^4.0.0" + minimatch "^3.0.4" + parse-package-name "^0.1.0" + path-is-inside "^1.0.2" + eslint-plugin-notice@^0.8.9: version "0.8.9" resolved "https://registry.npmjs.org/eslint-plugin-notice/-/eslint-plugin-notice-0.8.9.tgz#b3b55ba218b36a01fd4de998f68c66a8bf238a82" @@ -9123,6 +9174,14 @@ get-caller-file@^2.0.1: resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== +get-monorepo-packages@^1.1.0: + version "1.2.0" + resolved "https://registry.npmjs.org/get-monorepo-packages/-/get-monorepo-packages-1.2.0.tgz#3eee88d30b11a5f65955dec6ae331958b2a168e4" + integrity sha512-aDP6tH+eM3EuVSp3YyCutOcFS4Y9AhRRH9FAd+cjtR/g63Hx+DCXdKoP1ViRPUJz5wm+BOEXB4FhoffGHxJ7jQ== + dependencies: + globby "^7.1.1" + load-json-file "^4.0.0" + get-own-enumerable-property-symbols@^3.0.0: version "3.0.2" resolved "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" @@ -9448,6 +9507,18 @@ globby@^6.1.0: pify "^2.0.0" pinkie-promise "^2.0.0" +globby@^7.1.1: + version "7.1.1" + resolved "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz#fb2ccff9401f8600945dfada97440cca972b8680" + integrity sha1-+yzP+UAfhgCUXfral0QMypcrhoA= + dependencies: + array-union "^1.0.1" + dir-glob "^2.0.0" + glob "^7.1.2" + ignore "^3.3.5" + pify "^3.0.0" + slash "^1.0.0" + globby@^9.2.0: version "9.2.0" resolved "https://registry.npmjs.org/globby/-/globby-9.2.0.tgz#fd029a706c703d29bdd170f4b6db3a3f7a7cb63d" @@ -11207,14 +11278,6 @@ jest-environment-node@^25.1.0: jest-mock "^25.1.0" jest-util "^25.1.0" -jest-fetch-mock@^3.0.3: - version "3.0.3" - resolved "https://registry.npmjs.org/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz#31749c456ae27b8919d69824f1c2bd85fe0a1f3b" - integrity sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw== - dependencies: - cross-fetch "^3.0.4" - promise-polyfill "^8.1.3" - jest-get-type@^24.9.0: version "24.9.0" resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.9.0.tgz#1684a0c8a50f2e4901b6644ae861f579eed2ef0e" @@ -13520,11 +13583,6 @@ node-fetch-npm@^2.0.2: json-parse-better-errors "^1.0.0" safe-buffer "^5.1.1" -node-fetch@2.6.0, node-fetch@^2.3.0, node-fetch@^2.5.0, node-fetch@^2.6.0: - version "2.6.0" - resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" - integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== - node-fetch@^1.0.1: version "1.7.3" resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" @@ -13533,6 +13591,11 @@ node-fetch@^1.0.1: encoding "^0.1.11" is-stream "^1.0.1" +node-fetch@^2.3.0, node-fetch@^2.5.0, node-fetch@^2.6.0: + version "2.6.0" + resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" + integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== + node-forge@0.9.0: version "0.9.0" resolved "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579" @@ -14556,6 +14619,11 @@ parse-json@^5.0.0: json-parse-better-errors "^1.0.1" lines-and-columns "^1.1.6" +parse-package-name@^0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/parse-package-name/-/parse-package-name-0.1.0.tgz#3f44dd838feb4c2be4bf318bae4477d7706bade4" + integrity sha1-P0Tdg4/rTCvkvzGLrkR313BrreQ= + parse-passwd@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" @@ -15688,11 +15756,6 @@ promise-inflight@^1.0.1, promise-inflight@~1.0.1: resolved "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= -promise-polyfill@^8.1.3: - version "8.1.3" - resolved "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.3.tgz#8c99b3cf53f3a91c68226ffde7bde81d7f904116" - integrity sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g== - promise-retry@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/promise-retry/-/promise-retry-1.1.1.tgz#6739e968e3051da20ce6497fb2b50f6911df3d6d" @@ -16235,7 +16298,7 @@ react-router-dom@^5.1.2: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" -react-router@5.1.2: +react-router@5.1.2, react-router@^5.1.2: version "5.1.2" resolved "https://registry.npmjs.org/react-router/-/react-router-5.1.2.tgz#6ea51d789cb36a6be1ba5f7c0d48dd9e817d3418" integrity sha512-yjEuMFy1ONK246B+rsa0cUam5OeAQ8pyclRDgpxuSCrAlJ1qN9uZ5IgyKC7gQg0w8OM50NXHEegPh/ks9YuR2A== @@ -16861,7 +16924,7 @@ request@^2.85.0, request@^2.87.0, request@^2.88.0: tunnel-agent "^0.6.0" uuid "^3.3.2" -"request@github:cypress-io/request#b5af0d1fa47eec97ba980cde90a13e69a2afcd16": +request@cypress-io/request#b5af0d1fa47eec97ba980cde90a13e69a2afcd16: version "2.88.1" resolved "https://codeload.github.com/cypress-io/request/tar.gz/b5af0d1fa47eec97ba980cde90a13e69a2afcd16" dependencies: @@ -18929,6 +18992,16 @@ ts-pnp@1.1.6, ts-pnp@^1.1.2, ts-pnp@^1.1.6: resolved "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.1.6.tgz#389a24396d425a0d3162e96d2b4638900fdc289a" integrity sha512-CrG5GqAAzMT7144Cl+UIFP7mz/iIhiy+xQ6GGcnjTezhALT02uPMRw7tgDSESgB5MsfKt55+GPWw4ir1kVtMIQ== +tsconfig-paths@^3.9.0: + version "3.9.0" + resolved "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b" + integrity sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.1" + minimist "^1.2.0" + strip-bom "^3.0.0" + tslib@1.10.0: version "1.10.0" resolved "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" @@ -19639,7 +19712,7 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5: dependencies: iconv-lite "0.4.24" -whatwg-fetch@3.0.0, whatwg-fetch@>=0.10.0, whatwg-fetch@^3.0.0: +whatwg-fetch@>=0.10.0, whatwg-fetch@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==