diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml
index d571703294..612375ca1c 100644
--- a/.github/workflows/cli.yml
+++ b/.github/workflows/cli.yml
@@ -6,6 +6,7 @@ on:
- '.github/workflows/cli.yml'
- 'packages/cli/**'
- 'packages/core/**'
+ - 'scripts/**'
jobs:
build:
@@ -40,19 +41,27 @@ jobs:
- name: yarn install
run: yarn install --frozen-lockfile
- run: yarn build
- # This creates a new plugin and pollutes the workspace, so it should be run last.
- - name: verify app serve and plugin creation on Windows
+ - name: verify app and plugin creation on Windows
+ working-directory: ${{ runner.temp }}
if: runner.os == 'Windows'
- run: node scripts/cli-e2e-test.js
- - name: verify app serve and plugin creation on Linux
+ run: node ${{ github.workspace }}/packages/cli/e2e-test/cli-e2e-test.js
+ env:
+ BACKSTAGE_E2E_CLI_TEST: true
+ - name: verify app and plugin creation on Linux
+ working-directory: ${{ runner.temp }}
if: runner.os == 'Linux'
run: |
sudo sysctl fs.inotify.max_user_watches=524288
- node scripts/cli-e2e-test.js
- - name: yarn lint, test after plugin creation
- working-directory: plugins/test-plugin
- run: |
- yarn lint
- yarn test
+ node ${{ github.workspace }}/packages/cli/e2e-test/cli-e2e-test.js
env:
- CI: true
+ BACKSTAGE_E2E_CLI_TEST: true
+ - name: lint newly created app and plugin
+ run: yarn lint:all
+ working-directory: ${{ runner.temp }}/test-app
+ env:
+ BACKSTAGE_E2E_CLI_TEST: true
+ - name: test newly created app and plugin
+ run: yarn test:all
+ working-directory: ${{ runner.temp }}/test-app
+ env:
+ BACKSTAGE_E2E_CLI_TEST: true
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000000..68fb208caa
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,63 @@
+# Contributing
+
+Our vision for Backstage is for it to become the trusted standard toolbox (read: UX layer) for the open source infrastructure landscape. Think of it like Kubernetes for developer experience. We realize this is an ambitious goal. We canβt do it alone.
+
+Therefore we want to create strong community of contributors -- all working together to create the kind of delightful experience that our developers deserve.
+
+Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given. β€οΈ
+
+# Types of Contributions
+
+## Report bugs
+
+No one likes bugs. Report bugs as an issue [here](https://github.com/spotify/backstage/issues/new?template=bug_template.md).
+
+## Fix bugs or build new features
+
+Look through the GitHub issues for [bugs](https://github.com/spotify/backstage/labels/bugs), [good first issues](https://github.com/spotify/backstage/labels/good%20first%20issue) or [help wanted](https://github.com/spotify/backstage/labels/help%20wanted).
+
+## Build a plugin
+
+The value of Backstage grows with every new plugin that gets added. Wouldn't it be fantastic if there was a plugin for every infrastructure project out there? We think so. And we would love your help.
+
+What kind of plugins should/could be created? Some inspiration from the 120+ plugins that we have developed inside Spotify can be found [here](https://backstage.io/demos), but we will keep a running list of suggestions labeled with [[plugin]](https://github.com/spotify/backstage/labels/plugin).
+
+A great reference example of a plugin can be found on [our blog](https://backstage.io/blog/2020/04/06/lighthouse-plugin) (thanks [@fastfrwrd](https://github.com/fastfrwrd)!)
+
+## Write Documentation
+
+The current documentation is very limited. Help us make the `/docs` folder come alive.
+
+## Contribute to Storybook
+
+We think the best way to ensure different plugins provide a consistent experience is through a solid set of reusable UI/UX components. Backstage uses [Storybook](http://storybook.backstage.io).
+
+Either help us [create new components](https://github.com/spotify/backstage/labels/help%20wanted) or improve stories for the existing ones (look for files with `*.stories.tsx`).
+
+## Submit Feedback
+
+The best way to send feedback is to file [an issue](https://github.com/spotify/backstage/issues).
+
+If you are proposing a feature:
+
+- Explain in detail how it would work.
+- Keep the scope as narrow as possible, to make it easier to implement.
+- Use appropriate labels
+- Remember that this is a volunteer-driven project, and that contributions
+ are welcome :)
+
+# Get Started!
+
+So...feel ready to jump in? Let's do this. Head over to the [Getting Started guide](https://github.com/spotify/backstage#getting-started) ππ»π―
+
+If you need help, just jump into our [Discord chatroom](https://discord.gg/MUpMjP2).
+
+# Code of Conduct
+
+This project adheres to the [Spotify FOSS Code of Conduct][code-of-conduct]. By participating, you are expected to honor this code.
+
+[code-of-conduct]: https://github.com/spotify/backstage/blob/master/CODE_OF_CONDUCT.md
+
+# Security Issues?
+
+Please report sensitive security issues via Spotify's [bug-bounty program](https://hackerone.com/spotify) rather than GitHub.
diff --git a/README.md b/README.md
index c012638e7d..87a3de6138 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,7 @@

[](https://discord.gg/EBHEGzX)

+[](https://github.com/spotify/backstage/releases)
## What is Backstage?
@@ -59,23 +60,25 @@ To run a Backstage app, you will need to have the following installed:
- [NodeJS](https://nodejs.org/en/download/) - Active LTS Release, currently v12
- [yarn](https://classic.yarnpkg.com/en/docs/install)
-Open a terminal window and start the web app using the following commands from the project root:
+After cloning this repo, open a terminal window and start the web app using the following commands from the project root:
```bash
-$ yarn install # may take a while
-
-$ yarn start
+yarn install
+yarn start
```
The final `yarn start` command should open a local instance of Backstage in your browser, otherwise open one of the URLs printed in the terminal.
-For more complex development environment configuration, see the
-[Development Environment](docs/getting-started/development-environment.md) section of the Getting Started docs.
+And thats it! You are good to go π
+
+### Next step
+
+Take a look at the [Getting Started](docs/getting-started/README.md) guide to learn more about how to extend the functionality with Plugins.
## Documentation
-- [FAQs](docs/FAQ.md)
- [Getting Started](docs/getting-started/README.md)
+- [Create a Backstage App](docs/create-an-app.md)
- [Architecture](docs/architecture-terminology.md)
- [API references](docs/reference/README.md)
- [Designing for Backstage](docs/design.md)
@@ -95,6 +98,10 @@ For more complex development environment configuration, see the
Or, if you are an open source developer and are interested in joining our team, please reach out to [foss-opportunities@spotify.com ](mailto:foss-opportunities@spotify.com)
+## Contributing
+
+We would love your help in building Backstage! See [CONTRIBUTING](CONTRIBUTING.md) for more information.
+
## License
Copyright 2020 Spotify AB.
diff --git a/docs/getting-started/create-an-app.md b/docs/create-an-app.md
similarity index 92%
rename from docs/getting-started/create-an-app.md
rename to docs/create-an-app.md
index 703d1d59a5..8c8050c2ff 100644
--- a/docs/getting-started/create-an-app.md
+++ b/docs/create-an-app.md
@@ -17,7 +17,7 @@ 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.
-
+
Inside that directory, it will generate all the files and folder structure needed for you to run your app.
@@ -70,7 +70,3 @@ 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/create-app_output.png
similarity index 100%
rename from docs/getting-started/create-app_output.png
rename to docs/create-app_output.png
diff --git a/docs/getting-started/README.md b/docs/getting-started/README.md
index 163e52b950..064776e712 100644
--- a/docs/getting-started/README.md
+++ b/docs/getting-started/README.md
@@ -1,9 +1,10 @@
# Getting started with Backstage
-Here is a collection of tutorials that will guide you through setting up and extending an instance of Backstage with your own plugins.
+## Creating a Plugin
+
+The value of Backstage grows with every new plugin that gets added. 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/development-environment.md b/docs/getting-started/development-environment.md
index e4fe69af68..7f6e2bcb44 100644
--- a/docs/getting-started/development-environment.md
+++ b/docs/getting-started/development-environment.md
@@ -34,4 +34,6 @@ Then open http://localhost/ on your browser.
> See [package.json](/package.json) for other yarn commands/options.
+[Next Step - Create a Backstage plugin](create-a-plugin.md)
+
[Back to Docs](README.md)
diff --git a/lerna.json b/lerna.json
index 2cc439d190..1d77db92fb 100644
--- a/lerna.json
+++ b/lerna.json
@@ -5,5 +5,5 @@
],
"npmClient": "yarn",
"useWorkspaces": true,
- "version": "0.1.1-alpha.3"
+ "version": "0.1.1-alpha.4"
}
diff --git a/package.json b/package.json
index 0387a4e7ce..8a6c58618f 100644
--- a/package.json
+++ b/package.json
@@ -8,12 +8,14 @@
"start": "yarn workspace example-app start",
"bundle": "yarn build && yarn workspace example-app bundle",
"build": "lerna run build",
+ "clean": "lerna run clean",
"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",
"release": "if [ \"$(git symbolic-ref --short HEAD)\" = master ]; then echo \"don't try to release master\"; exit 1; else lerna version --no-push; fi",
"lerna": "lerna",
"storybook": "yarn workspace storybook start"
@@ -32,8 +34,7 @@
"lerna": "^3.20.2",
"lint-staged": "^10.1.0",
"prettier": "^1.19.1",
- "typescript": "^3.7.5",
- "zombie": "^6.1.4"
+ "typescript": "^3.7.5"
},
"dependencies": {
"@types/classnames": "^2.2.9",
diff --git a/packages/app/package.json b/packages/app/package.json
index 232a00416d..fd9aa88323 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -1,14 +1,14 @@
{
"name": "example-app",
- "version": "0.1.1-alpha.3",
+ "version": "0.1.1-alpha.4",
"private": true,
"dependencies": {
- "@backstage/cli": "^0.1.1-alpha.3",
- "@backstage/core": "^0.1.1-alpha.3",
- "@backstage/theme": "^0.1.1-alpha.3",
- "@backstage/plugin-home-page": "^0.1.1-alpha.3",
- "@backstage/plugin-lighthouse": "^0.1.1-alpha.3",
- "@backstage/plugin-welcome": "^0.1.1-alpha.3",
+ "@backstage/cli": "^0.1.1-alpha.4",
+ "@backstage/core": "^0.1.1-alpha.4",
+ "@backstage/plugin-home-page": "^0.1.1-alpha.4",
+ "@backstage/plugin-lighthouse": "^0.1.1-alpha.4",
+ "@backstage/plugin-welcome": "^0.1.1-alpha.4",
+ "@backstage/theme": "^0.1.1-alpha.4",
"@material-ui/core": "^4.9.1",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -30,6 +30,7 @@
"scripts": {
"start": "backstage-cli app:serve",
"bundle": "backstage-cli app:build",
+ "clean": "backstage-cli clean",
"test": "backstage-cli test",
"test:e2e": "start-server-and-test start http://localhost:3000 cy:dev",
"test:e2e:ci": "start-server-and-test start http://localhost:3000 cy:run",
diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx
index 7f203db196..3b0a8c894b 100644
--- a/packages/app/src/App.tsx
+++ b/packages/app/src/App.tsx
@@ -20,7 +20,7 @@ import {
Theme,
ThemeProvider,
} from '@material-ui/core';
-import { BackstageThemeLight, BackstageThemeDark } from '@backstage/theme';
+import { lightTheme, darkTheme } from '@backstage/theme';
import { createApp } from '@backstage/core';
import React, { FC } from 'react';
import { BrowserRouter as Router } from 'react-router-dom';
@@ -53,30 +53,28 @@ 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: T = BackstageThemeLight;
+ let backstageTheme = lightTheme;
switch (theme) {
case 'light':
- backstageTheme = BackstageThemeLight;
+ backstageTheme = lightTheme;
break;
case 'dark':
- backstageTheme = BackstageThemeDark;
+ backstageTheme = darkTheme;
break;
default:
if (!window.matchMedia) {
- backstageTheme = BackstageThemeLight;
+ backstageTheme = lightTheme;
break;
}
backstageTheme = window.matchMedia('(prefers-color-scheme: dark)').matches
- ? BackstageThemeDark
- : BackstageThemeLight;
+ ? darkTheme
+ : lightTheme;
break;
}
diff --git a/packages/cli/bin/backstage-cli b/packages/cli/bin/backstage-cli
index a8ba74f28a..5288f913d9 100755
--- a/packages/cli/bin/backstage-cli
+++ b/packages/cli/bin/backstage-cli
@@ -19,7 +19,8 @@ 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) {
+
+if (!isLocal || process.env.BACKSTAGE_E2E_CLI_TEST) {
// 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.
diff --git a/packages/cli/e2e-test/.eslintrc.js b/packages/cli/e2e-test/.eslintrc.js
new file mode 100644
index 0000000000..274c7426b8
--- /dev/null
+++ b/packages/cli/e2e-test/.eslintrc.js
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+module.exports = {
+ rules: {
+ 'import/no-extraneous-dependencies': [
+ 'error',
+ {
+ devDependencies: true,
+ optionalDependencies: true,
+ peerDependencies: true,
+ bundledDependencies: true,
+ },
+ ],
+ },
+};
diff --git a/packages/cli/e2e-test/cli-e2e-test.js b/packages/cli/e2e-test/cli-e2e-test.js
new file mode 100644
index 0000000000..c8374af0eb
--- /dev/null
+++ b/packages/cli/e2e-test/cli-e2e-test.js
@@ -0,0 +1,81 @@
+/*
+ * 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 os = require('os');
+const fs = require('fs-extra');
+const { resolve: resolvePath } = require('path');
+const Browser = require('zombie');
+
+const {
+ spawnPiped,
+ handleError,
+ waitForPageWithText,
+ waitForExit,
+ print,
+} = require('./helpers');
+
+const createTestApp = require('./createTestApp');
+const createTestPlugin = require('./createTestPlugin');
+
+Browser.localhost('localhost', 3000);
+
+async function createTempDir() {
+ return fs.mkdtemp(resolvePath(os.tmpdir(), 'backstage-e2e-'));
+}
+
+async function main() {
+ process.env.BACKSTAGE_E2E_CLI_TEST = 'true';
+
+ const workDir = process.env.CI ? process.cwd() : await createTempDir();
+
+ process.stdout.write(`Initial directory: ${process.cwd()}\n`);
+ process.chdir(workDir);
+ process.stdout.write(`Working directory: ${process.cwd()}\n`);
+
+ await createTestApp();
+
+ const appDir = resolvePath(workDir, 'test-app');
+ process.chdir(appDir);
+ process.stdout.write(`App directory: ${appDir}\n`);
+
+ await createTestPlugin();
+
+ print('Starting the app');
+ const startApp = spawnPiped(['yarn', 'start']);
+
+ try {
+ const browser = new Browser();
+
+ await waitForPageWithText(browser, '/', 'Welcome to Backstage');
+ await waitForPageWithText(
+ browser,
+ '/test-plugin',
+ 'Welcome to test-plugin!',
+ );
+
+ print('Both App and Plugin loaded correctly');
+ } finally {
+ startApp.kill();
+ }
+
+ await waitForExit(startApp);
+
+ print('All tests done');
+ process.exit(0);
+}
+
+process.on('unhandledRejection', handleError);
+main(process.argv.slice(2)).catch(handleError);
diff --git a/packages/cli/e2e-test/createTestApp.js b/packages/cli/e2e-test/createTestApp.js
new file mode 100644
index 0000000000..5437804975
--- /dev/null
+++ b/packages/cli/e2e-test/createTestApp.js
@@ -0,0 +1,44 @@
+/*
+ * 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 { resolve: resolvePath } = require('path');
+const { spawnPiped, waitFor, waitForExit, print } = require('./helpers');
+
+async function createTestApp() {
+ const cliPath = resolvePath(__dirname, '../bin/backstage-cli');
+
+ print('Creating a Backstage App');
+ const createApp = spawnPiped(['node', cliPath, 'create-app']);
+
+ try {
+ let stdout = '';
+ createApp.stdout.on('data', data => {
+ stdout = stdout + data.toString('utf8');
+ });
+
+ await waitFor(() => stdout.includes('Enter a name for the app'));
+ createApp.stdin.write('test-app\n');
+
+ print('Waiting for app create script to be done');
+ await waitForExit(createApp);
+
+ print('Test app created');
+ } finally {
+ createApp.kill();
+ }
+}
+
+module.exports = createTestApp;
diff --git a/packages/cli/e2e-test/createTestPlugin.js b/packages/cli/e2e-test/createTestPlugin.js
new file mode 100644
index 0000000000..59e8d6dd77
--- /dev/null
+++ b/packages/cli/e2e-test/createTestPlugin.js
@@ -0,0 +1,44 @@
+/*
+ * 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 { spawnPiped, waitFor, waitForExit, print } = require('./helpers');
+
+async function createTestPlugin() {
+ print('Creating a Backstage Plugin');
+ const createPlugin = spawnPiped(['yarn', 'create-plugin']);
+
+ try {
+ let stdout = '';
+ createPlugin.stdout.on('data', data => {
+ stdout = stdout + data.toString('utf8');
+ });
+
+ await waitFor(() => stdout.includes('Enter an ID for the plugin'));
+ createPlugin.stdin.write('test-plugin\n');
+
+ // await waitFor(() => stdout.includes('Enter the owner(s) of the plugin'));
+ // createPlugin.stdin.write('@someuser\n');
+
+ print('Waiting for plugin create script to be done');
+ await waitForExit(createPlugin);
+
+ print('Test plugin created');
+ } finally {
+ createPlugin.kill();
+ }
+}
+
+module.exports = createTestPlugin;
diff --git a/scripts/cli-e2e-test.js b/packages/cli/e2e-test/helpers.js
similarity index 68%
rename from scripts/cli-e2e-test.js
rename to packages/cli/e2e-test/helpers.js
index 48058550d9..7e34f10a63 100644
--- a/scripts/cli-e2e-test.js
+++ b/packages/cli/e2e-test/helpers.js
@@ -14,92 +14,11 @@
* limitations under the License.
*/
-const { resolve: resolvePath } = require('path');
const childProcess = require('child_process');
const { spawn } = childProcess;
-const Browser = require('zombie');
const EXPECTED_LOAD_ERRORS = /ECONNREFUSED|ECONNRESET|did not get to load all resources/;
-Browser.localhost('localhost', 3000);
-
-async function main() {
- process.env.CI = 'true';
-
- const projectDir = resolvePath(__dirname, '..');
- process.chdir(projectDir);
-
- const start = spawnPiped(['yarn', 'start']);
-
- try {
- const browser = new Browser();
-
- await waitForPageWithText(browser, '/', 'Welcome to Backstage');
- print('Backstage loaded correctly, creating plugin');
-
- const createPlugin = spawnPiped(['yarn', 'create-plugin']);
-
- let stdout = '';
- createPlugin.stdout.on('data', data => {
- stdout = stdout + data.toString('utf8');
- });
-
- await waitFor(() => stdout.includes('Enter an ID for the plugin'));
- createPlugin.stdin.write('test-plugin\n');
-
- await waitFor(() => stdout.includes('Enter the owner(s) of the plugin'));
- createPlugin.stdin.write('@someuser\n');
-
- print('Waiting for plugin create script to be done');
- await waitForExit(createPlugin);
-
- print('Plugin create script is done, waiting for plugin page to load');
- await waitForPageWithText(
- browser,
- '/test-plugin',
- 'Welcome to test-plugin!',
- );
-
- print('Test plugin loaded correctly, exiting');
- } finally {
- start.kill();
- }
- await waitForExit(start);
-
- process.exit(0);
-}
-
-function waitFor(fn) {
- return new Promise(resolve => {
- const handle = setInterval(() => {
- if (fn()) {
- clearInterval(handle);
- resolve();
- return;
- }
- }, 100);
- });
-}
-
-function print(msg) {
- return process.stdout.write(`${msg}\n`);
-}
-
-async function waitForExit(child) {
- if (child.exitCode !== null) {
- throw new Error(`Child already exited with code ${child.exitCode}`);
- }
- await new Promise((resolve, reject) =>
- child.once('exit', code => {
- if (code) {
- reject(new Error(`Child exited with code ${code}`));
- } else {
- resolve();
- }
- }),
- );
-}
-
function spawnPiped(cmd, options) {
function pipeWithPrefix(stream, prefix = '') {
return data => {
@@ -135,11 +54,48 @@ function spawnPiped(cmd, options) {
return child;
}
+function handleError(err) {
+ process.stdout.write(`${err.name}: ${err.stack || err.message}\n`);
+ if (typeof err.code === 'number') {
+ process.exit(err.code);
+ } else {
+ process.exit(1);
+ }
+}
+
+function waitFor(fn) {
+ return new Promise(resolve => {
+ const handle = setInterval(() => {
+ if (fn()) {
+ clearInterval(handle);
+ resolve();
+ return;
+ }
+ }, 100);
+ });
+}
+
+async function waitForExit(child) {
+ if (child.exitCode !== null) {
+ throw new Error(`Child already exited with code ${child.exitCode}`);
+ }
+ await new Promise((resolve, reject) =>
+ child.once('exit', code => {
+ if (code) {
+ reject(new Error(`Child exited with code ${code}`));
+ } else {
+ print('Child finished');
+ resolve();
+ }
+ }),
+ );
+}
+
async function waitForPageWithText(
browser,
path,
text,
- { intervalMs = 1000, maxAttempts = 120 } = {},
+ { intervalMs = 1000, maxAttempts = 240 } = {},
) {
let attempts = 0;
for (;;) {
@@ -169,14 +125,15 @@ async function waitForPageWithText(
);
}
-function handleError(err) {
- process.stdout.write(`${err.name}: ${err.stack || err.message}\n`);
- if (typeof err.code === 'number') {
- process.exit(err.code);
- } else {
- process.exit(1);
- }
+function print(msg) {
+ return process.stdout.write(`${msg}\n`);
}
-process.on('unhandledRejection', handleError);
-main(process.argv.slice(2)).catch(handleError);
+module.exports = {
+ spawnPiped,
+ handleError,
+ waitFor,
+ waitForExit,
+ waitForPageWithText,
+ print,
+};
diff --git a/packages/cli/package.json b/packages/cli/package.json
index 1764d19bf2..faf8710d14 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.3",
+ "version": "0.1.1-alpha.4",
"private": false,
"publishConfig": {
"access": "public"
@@ -22,6 +22,8 @@
"build": "backstage-cli build-cache -- tsc",
"lint": "backstage-cli lint",
"test": "backstage-cli test",
+ "test:e2e": "node e2e-test/cli-e2e-test.js",
+ "clean": "backstage-cli clean",
"start": "nodemon ."
},
"devDependencies": {
@@ -40,7 +42,8 @@
"del": "^5.1.0",
"nodemon": "^2.0.2",
"ts-node": "^8.6.2",
- "tsconfig-paths": "^3.9.0"
+ "tsconfig-paths": "^3.9.0",
+ "zombie": "^6.1.4"
},
"bin": {
"backstage-cli": "bin/backstage-cli"
diff --git a/packages/cli/src/commands/clean/clean.ts b/packages/cli/src/commands/clean/clean.ts
new file mode 100644
index 0000000000..f18250d210
--- /dev/null
+++ b/packages/cli/src/commands/clean/clean.ts
@@ -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 fs from 'fs-extra';
+import { resolve as resolvePath, relative as relativePath } from 'path';
+import { getDefaultCacheOptions } from 'commands/build-cache/options';
+import { paths } from 'helpers/paths';
+
+export default async function clean() {
+ const cacheOptions = getDefaultCacheOptions();
+ const packagePath = getPackagePath(cacheOptions.cacheDir);
+ await fs.remove(cacheOptions.output);
+ await fs.remove(packagePath);
+}
+
+function getPackagePath(cacheDir: string) {
+ const relativePackagePath = relativePath(paths.targetRoot, paths.targetDir);
+ const packagePath = resolvePath(cacheDir, relativePackagePath);
+ return packagePath;
+}
diff --git a/packages/cli/src/commands/create-app/createApp.ts b/packages/cli/src/commands/create-app/createApp.ts
index 5863d9e705..cb34dc0686 100644
--- a/packages/cli/src/commands/create-app/createApp.ts
+++ b/packages/cli/src/commands/create-app/createApp.ts
@@ -86,6 +86,30 @@ export async function moveApp(
});
}
+async function addPackageResolutions(appDir: string) {
+ const pkgJsonPath = resolvePath(appDir, 'package.json');
+ const packageFileContent = await fs.readFile(pkgJsonPath, 'utf-8');
+ const packageFileJson = JSON.parse(packageFileContent);
+
+ packageFileJson.resolutions = packageFileJson.resolutions || {};
+
+ const packages = ['cli', 'core', 'test-utils', 'test-utils-core', 'theme'];
+
+ for (const pkg of packages) {
+ await Task.forItem('adding', `${pkg} link to package.json`, async () => {
+ const pkgPath = paths.resolveOwnRoot('packages', pkg);
+ packageFileJson.resolutions[`@backstage/${pkg}`] = `file:${pkgPath}`;
+ const newContents = `${JSON.stringify(packageFileJson, null, 2)}\n`;
+
+ await fs.writeFile(pkgJsonPath, newContents, 'utf-8').catch(error => {
+ throw new Error(
+ `Failed to add resolutions to package.json: ${error.message}`,
+ );
+ });
+ });
+ }
+}
+
export default async () => {
const questions: Question[] = [
{
@@ -126,6 +150,12 @@ export default async () => {
Task.section('Moving to final location');
await moveApp(tempDir, appDir, answers.name);
+ // e2e testing needs special treatment
+ if (process.env.BACKSTAGE_E2E_CLI_TEST) {
+ Task.section('Linking packages locally for e2e tests');
+ await addPackageResolutions(appDir);
+ }
+
Task.section('Building the app');
await buildApp(appDir);
@@ -134,6 +164,7 @@ export default async () => {
chalk.green(`π₯ Successfully created ${chalk.cyan(answers.name)}`),
);
Task.log();
+ Task.exit();
} catch (error) {
Task.error(error.message);
@@ -143,5 +174,6 @@ export default async () => {
Task.section('Cleanup');
await cleanUp(tempDir);
Task.error('π₯ Failed to create app!');
+ Task.exit(1);
}
};
diff --git a/packages/cli/src/commands/create-plugin/createPlugin.ts b/packages/cli/src/commands/create-plugin/createPlugin.ts
index df8b71f619..1af5832818 100644
--- a/packages/cli/src/commands/create-plugin/createPlugin.ts
+++ b/packages/cli/src/commands/create-plugin/createPlugin.ts
@@ -65,19 +65,18 @@ const sortObjectByKeys = (obj: { [name in string]: string }) => {
}, {} as { [name in string]: string });
};
-const capitalize = (str: string): string =>
+export const capitalize = (str: string): string =>
str.charAt(0).toUpperCase() + str.slice(1);
-const addExportStatement = async (
+export const addExportStatement = async (
file: string,
- importStatement: string,
exportStatement: string,
) => {
const newContents = fs
.readFileSync(file, 'utf8')
.split('\n')
.filter(Boolean) // get rid of empty lines
- .concat([importStatement, exportStatement])
+ .concat([exportStatement])
.concat(['']) // newline at end of file
.join('\n');
@@ -122,19 +121,16 @@ export async function addPluginToApp(rootDir: string, pluginName: string) {
.split('-')
.map(name => capitalize(name))
.join('');
- const pluginImport = `import { plugin as ${pluginNameCapitalized} } from '${pluginPackage}';`;
- const pluginExport = `export { ${pluginNameCapitalized} };`;
+ const pluginExport = `export { plugin as ${pluginNameCapitalized} } from '${pluginPackage}';`;
const pluginsFilePath = 'packages/app/src/plugins.ts';
const pluginsFile = resolvePath(rootDir, pluginsFilePath);
await Task.forItem('processing', pluginsFilePath, async () => {
- await addExportStatement(pluginsFile, pluginImport, pluginExport).catch(
- error => {
- throw new Error(
- `Failed to import plugin in app: ${pluginsFile}: ${error.message}`,
- );
- },
- );
+ await addExportStatement(pluginsFile, pluginExport).catch(error => {
+ throw new Error(
+ `Failed to import plugin in app: ${pluginsFile}: ${error.message}`,
+ );
+ });
});
}
@@ -268,6 +264,7 @@ export default async () => {
)}`,
);
Task.log();
+ Task.exit();
} catch (error) {
Task.error(error.message);
@@ -277,5 +274,6 @@ export default async () => {
Task.section('Cleanup');
await cleanUp(tempDir);
Task.error('π₯ Failed to create plugin!');
+ Task.exit(1);
}
};
diff --git a/packages/cli/src/commands/plugin/rollup.config.ts b/packages/cli/src/commands/plugin/rollup.config.ts
index fe2675e0e0..c991fa3efb 100644
--- a/packages/cli/src/commands/plugin/rollup.config.ts
+++ b/packages/cli/src/commands/plugin/rollup.config.ts
@@ -46,6 +46,7 @@ export default {
json(),
typescript({
include: `${paths.resolveTarget('src')}/**/*.{js,jsx,ts,tsx}`,
+ clean: true,
}),
],
} as RollupWatchOptions;
diff --git a/packages/cli/src/commands/remove-plugin/removePlugin.test.ts b/packages/cli/src/commands/remove-plugin/removePlugin.test.ts
new file mode 100644
index 0000000000..1cd47864c6
--- /dev/null
+++ b/packages/cli/src/commands/remove-plugin/removePlugin.test.ts
@@ -0,0 +1,193 @@
+/*
+ * 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 fse from 'fs-extra';
+import path from 'path';
+import os from 'os';
+import { paths } from '../../helpers/paths';
+import {
+ addExportStatement,
+ capitalize,
+ createTemporaryPluginFolder,
+} from '../create-plugin/createPlugin';
+import { addCodeownersEntry } from '../create-plugin/lib/codeowners';
+import {
+ removeReferencesFromAppPackage,
+ removeReferencesFromPluginsFile,
+ removePluginDirectory,
+ removeSymLink,
+ removePluginFromCodeOwners,
+} from './removePlugin';
+
+// Some constant variables
+const BACKSTAGE = `@backstage`;
+const testPluginName = 'yarn-test-package';
+const testPluginPackage = `${BACKSTAGE}/plugin-${testPluginName}`;
+const tempDir = path.join(os.tmpdir(), 'remove-plugin-test');
+
+const removeEmptyLines = (file: string): string =>
+ file
+ .split('\n')
+ .filter(Boolean)
+ .join('\n');
+
+const createTestPackageFile = async (
+ testFilePath: string,
+ packageFile: string,
+) => {
+ // Copy contents of package file for test
+ const packageFileContent = JSON.parse(fse.readFileSync(packageFile, 'utf8'));
+
+ packageFileContent.dependencies[testPluginPackage] = '0.1.0';
+ fse.createFileSync(testFilePath);
+ fse.writeFileSync(
+ testFilePath,
+ `${JSON.stringify(packageFileContent, null, 2)}\n`,
+ 'utf8',
+ );
+ return;
+};
+
+const createTestPluginFile = async (
+ testFilePath: string,
+ pluginsFilePath: string,
+) => {
+ // Copy contents of package file for test
+ fse.copyFileSync(pluginsFilePath, testFilePath);
+ const pluginNameCapitalized = testPluginName
+ .split('-')
+ .map(name => capitalize(name))
+ .join('');
+ const exportStatement = `export { default as ${pluginNameCapitalized}} from @backstage/plugin-${testPluginName}`;
+ addExportStatement(testFilePath, exportStatement);
+};
+
+const mkTestPluginDir = (testDirPath: string) => {
+ fse.mkdirSync(testDirPath);
+ for (let i = 0; i < 50; i++)
+ fse.createFileSync(path.join(testDirPath, `testFile${i}.ts`));
+};
+
+beforeAll(() => {
+ // Create temporary directory for all tests
+ createTemporaryPluginFolder(tempDir);
+});
+
+describe('removePlugin', () => {
+ describe('Remove Plugin Dependencies', () => {
+ const appPath = paths.resolveTargetRoot('packages', 'app');
+ const githubDir = paths.resolveTargetRoot('.github');
+ it('removes plugin references from /packages/app/package.json', async () => {
+ // Set up test
+ const packageFilePath = path.join(appPath, 'package.json');
+ const testFilePath = path.join(tempDir, 'test.json');
+ createTestPackageFile(testFilePath, packageFilePath);
+ try {
+ await removeReferencesFromAppPackage(testFilePath, testPluginName);
+ const testFileContent = removeEmptyLines(
+ fse.readFileSync(testFilePath, 'utf8'),
+ );
+ const packageFileContent = removeEmptyLines(
+ fse.readFileSync(packageFilePath, 'utf8'),
+ );
+ expect(testFileContent === packageFileContent).toBe(true);
+ } finally {
+ fse.removeSync(testFilePath);
+ }
+ });
+ it('removes plugin exports from /packages/app/src/packacge.json', async () => {
+ const testFilePath = path.join(tempDir, 'test.ts');
+ const pluginsFilePaths = path.join(appPath, 'src', 'plugins.ts');
+ createTestPluginFile(testFilePath, pluginsFilePaths);
+ try {
+ await removeReferencesFromPluginsFile(testFilePath, testPluginName);
+ const testFileContent = removeEmptyLines(
+ fse.readFileSync(testFilePath, 'utf8'),
+ );
+ const pluginsFileContent = removeEmptyLines(
+ fse.readFileSync(pluginsFilePaths, 'utf8'),
+ );
+ expect(testFileContent === pluginsFileContent).toBe(true);
+ } finally {
+ fse.removeSync(testFilePath);
+ }
+ });
+ it('removes codeOwners references', async () => {
+ const testFilePath = path.join(tempDir, 'test');
+ const codeownersPath = path.join(githubDir, 'CODEOWNERS');
+ try {
+ fse.copySync(codeownersPath, testFilePath);
+ const testFileContent = removeEmptyLines(
+ fse.readFileSync(testFilePath, 'utf8'),
+ );
+ const codeOwnersFileContent = removeEmptyLines(
+ fse.readFileSync(codeownersPath, 'utf8'),
+ );
+ await addCodeownersEntry(testFilePath!, `/plugins/${testPluginName}`, [
+ '@thisIsAtestTeam',
+ 'test@gmail.com',
+ ]);
+ await removePluginFromCodeOwners(testFilePath, testPluginName);
+ expect(testFileContent === codeOwnersFileContent).toBeTruthy();
+ } finally {
+ if (fse.existsSync(testFilePath)) fse.removeSync(testFilePath);
+ }
+ });
+ });
+ describe('Remove files', () => {
+ const testDirPath = path.join(
+ paths.resolveTargetRoot(),
+ 'plugins',
+ testPluginName,
+ );
+ describe('Removes Plugin Directory', () => {
+ it('removes plugin directory from /plugins', async () => {
+ try {
+ mkTestPluginDir(testDirPath);
+ expect(fse.existsSync(testDirPath)).toBeTruthy();
+ await removePluginDirectory(testDirPath);
+ expect(fse.existsSync(testDirPath)).toBeFalsy();
+ } finally {
+ if (fse.existsSync(testDirPath)) fse.removeSync(testDirPath);
+ }
+ });
+ });
+ describe('Removes System Link', () => {
+ it('removes system link from @backstage', async () => {
+ const scopedDir = paths.resolveTargetRoot('node_modules', '@backstage');
+ const testSymLinkPath = path.join(
+ scopedDir,
+ `plugin-${testPluginName}`,
+ );
+ try {
+ mkTestPluginDir(testDirPath);
+ fse.ensureSymlinkSync(testSymLinkPath, testDirPath);
+
+ await removeSymLink(testSymLinkPath);
+ expect(fse.existsSync(testSymLinkPath)).toBeFalsy();
+ } finally {
+ if (fse.existsSync(testDirPath)) fse.removeSync(testDirPath);
+ if (fse.existsSync(testSymLinkPath)) fse.removeSync(testSymLinkPath);
+ }
+ });
+ });
+ });
+});
+
+afterAll(() => {
+ // Remove temporary directory
+ fse.removeSync(tempDir);
+});
diff --git a/packages/cli/src/commands/remove-plugin/removePlugin.ts b/packages/cli/src/commands/remove-plugin/removePlugin.ts
new file mode 100644
index 0000000000..76b8606973
--- /dev/null
+++ b/packages/cli/src/commands/remove-plugin/removePlugin.ts
@@ -0,0 +1,257 @@
+/*
+ * 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 fse from 'fs-extra';
+import path from 'path';
+import chalk from 'chalk';
+import inquirer, { Answers, Question } from 'inquirer';
+import { getCodeownersFilePath } from '../create-plugin/lib/codeowners';
+import { paths } from 'helpers/paths';
+import { Task } from 'helpers/tasks';
+// import os from 'os';
+
+const BACKSTAGE = '@backstage';
+
+export const checkExists = async (rootDir: string, pluginName: string) => {
+ await Task.forItem('checking', pluginName, async () => {
+ try {
+ const destination = path.join(rootDir, 'plugins', pluginName);
+ const pathExist = await fse.pathExists(destination);
+
+ if (!pathExist) {
+ throw new Error(
+ chalk.red(` Plugin ${chalk.cyan(pluginName)} does not exist!`),
+ );
+ }
+ } catch (e) {
+ throw new Error(
+ chalk.red(
+ ` There was an error removing plugin ${chalk.cyan(pluginName)}: ${
+ e.message
+ }`,
+ ),
+ );
+ }
+ });
+};
+
+export const removePluginDirectory = async (destination: string) => {
+ await Task.forItem('removing', 'plugin files', async () => {
+ try {
+ await fse.remove(destination);
+ } catch (e) {
+ throw Error(
+ chalk.red(
+ ` There was a problem removing the plugin directory: ${e.message}`,
+ ),
+ );
+ }
+ });
+};
+
+export const removeSymLink = async (destination: string) => {
+ await Task.forItem('removing', 'symbolic link', async () => {
+ const symLinkExists = fse.pathExists(destination);
+ if (symLinkExists) {
+ try {
+ await fse.remove(destination);
+ } catch (e) {
+ throw Error(
+ chalk.red(
+ ` Could not remove symbolic link\t${chalk.cyan(destination)}: ${
+ e.message
+ }`,
+ ),
+ );
+ }
+ }
+ });
+};
+
+const removeAllStatementsContainingID = async (file: string, ID: string) => {
+ const originalContent = await fse.readFile(file, 'utf8');
+ const contentAfterRemoval = originalContent
+ .split('\n')
+ .filter(Boolean) // get rid of empty lines
+ .filter(statement => {
+ return !statement.includes(`${ID}`);
+ }) // get rid of lines with pluginName
+ .concat(['']) // newline at end of line
+ .join('\n');
+ await fse.writeFile(file, contentAfterRemoval, 'utf8');
+ const finalContent = await fse.readFile(file, 'utf8');
+ if (finalContent === originalContent)
+ throw new Error(`File was not modified.`);
+};
+
+const capitalize = (str: string): string =>
+ str.charAt(0).toUpperCase() + str.slice(1);
+
+export const removeReferencesFromPluginsFile = async (
+ pluginsFile: string,
+ pluginName: string,
+) => {
+ const pluginNameCapitalized = pluginName
+ .split('-')
+ .map(name => capitalize(name))
+ .join('');
+
+ await Task.forItem('removing', 'export references', async () => {
+ try {
+ await removeAllStatementsContainingID(pluginsFile, pluginNameCapitalized);
+ } catch (e) {
+ throw new Error(
+ chalk.red(
+ ` There was an error removing export statement for plugin ${chalk.cyan(
+ pluginNameCapitalized,
+ )}: ${e.message}`,
+ ),
+ );
+ }
+ });
+};
+
+export const removePluginFromCodeOwners = async (
+ codeOwnersFile: string,
+ pluginName: string,
+) => {
+ await Task.forItem('removing', 'codeowners references', async () => {
+ try {
+ await removeAllStatementsContainingID(codeOwnersFile, pluginName);
+ } catch (e) {
+ throw new Error(
+ chalk.red(
+ ` There was an error removing code owners statement for plugin ${chalk.cyan(
+ pluginName,
+ )}: ${e.message}`,
+ ),
+ );
+ }
+ });
+};
+
+export const removeReferencesFromAppPackage = async (
+ appPackageFile: string,
+ pluginName: string,
+) => {
+ const pluginPackage = `${BACKSTAGE}/plugin-${pluginName}`;
+
+ await Task.forItem('removing', 'plugin app dependency', async () => {
+ try {
+ const appPackageFileContent = await fse.readFile(appPackageFile, 'utf-8');
+ const appPackageFileContentJSON = JSON.parse(appPackageFileContent);
+ const dependencies = appPackageFileContentJSON.dependencies;
+
+ if (!dependencies[pluginPackage]) {
+ throw new Error(
+ chalk.red(
+ ` Plugin ${chalk.cyan(
+ pluginPackage,
+ )} does not exist in ${chalk.cyan(appPackageFile)}`,
+ ),
+ );
+ }
+
+ delete dependencies[pluginPackage];
+ await fse.writeFile(
+ appPackageFile,
+ `${JSON.stringify(appPackageFileContentJSON, null, 2)}\n`,
+ 'utf-8',
+ );
+ } catch (e) {
+ throw new Error(
+ chalk.red(
+ ` Failed to remove plugin as dependency in app: ${chalk.cyan(
+ appPackageFile,
+ )}: ${e.message}`,
+ ),
+ );
+ }
+ });
+};
+
+export default async () => {
+ const questions: Question[] = [
+ {
+ type: 'input',
+ name: 'pluginName',
+ message: chalk.blue(
+ 'Enter the ID of the plugin to be removed [required]',
+ ),
+ validate: (value: any) => {
+ if (!value) {
+ return chalk.red('Please enter an ID for the plugin');
+ } else if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(value)) {
+ return chalk.red(
+ 'Plugin IDs must be kehbab-cased and contain only letters, digits and dashes.',
+ );
+ }
+ return true;
+ },
+ },
+ ];
+
+ const answers: Answers = await inquirer.prompt(questions);
+ const pluginName: string = answers.pluginName;
+ const appPackage = paths.resolveTargetRoot('packages/app');
+ const pluginDir = paths.resolveTargetRoot('plugins', answers.pluginName);
+ const codeOwnersFile = await getCodeownersFilePath(paths.targetRoot);
+ const appPackageFile = path.join(appPackage, 'package.json');
+ const appPluginsFile = path.join(appPackage, 'src', 'plugins.ts');
+ const pluginScopedDirectory = paths.resolveTargetRoot(
+ 'node_modules',
+ BACKSTAGE,
+ `plugin-${pluginName}`,
+ );
+
+ Task.log();
+ Task.log('Removing the plugin...');
+
+ console.log(pluginScopedDirectory);
+ try {
+ Task.section('Checking the plugin exists.');
+ await checkExists(paths.targetRoot, pluginName);
+
+ Task.section('Removing plugin files.');
+ await removePluginDirectory(pluginDir);
+
+ Task.section('Removing symbolic link from @backstage.');
+ await removeSymLink(pluginScopedDirectory);
+
+ if (await fse.pathExists(appPackage)) {
+ Task.section('Removing references from plugins.ts.');
+ await removeReferencesFromPluginsFile(appPluginsFile, pluginName);
+
+ Task.section('Removing plugin dependency from app.');
+ await removeReferencesFromAppPackage(appPackageFile, pluginName);
+ }
+
+ if (codeOwnersFile) {
+ Task.section('Removing codeowners reference.');
+ await removePluginFromCodeOwners(codeOwnersFile, pluginName);
+ }
+
+ Task.log();
+ Task.log(
+ `π₯ Successfully removed ${chalk.cyan(
+ `@backstage/plugin-${answers.id}`,
+ )}`,
+ );
+ Task.log();
+ } catch (error) {
+ Task.error(error.message);
+ Task.log('It seems that something went wrong when removing the plugin π€');
+ }
+};
diff --git a/packages/cli/src/commands/watch-deps/index.ts b/packages/cli/src/commands/watch-deps/index.ts
index 11093d7387..68d2417e36 100644
--- a/packages/cli/src/commands/watch-deps/index.ts
+++ b/packages/cli/src/commands/watch-deps/index.ts
@@ -23,6 +23,7 @@ import { startCompiler } from './compiler';
import { startChild } from './child';
import { waitForExit, run } from 'helpers/run';
import { paths } from 'helpers/paths';
+import { Command } from 'commander';
const PACKAGE_BLACKLIST = [
// We never want to watch for changes in the cli, but all packages will depend on it.
@@ -88,8 +89,14 @@ export async function watchDeps(options: Options = {}) {
* and instead start up watch mode for that package. Starting watch mode means running the first
* available yarn script out of "build:watch", "watch", or "build" --watch.
*/
-export default async (_command: any, args: string[]) => {
- await watchDeps();
+export default async (cmd: Command, args: string[]) => {
+ const options: Options = {};
+
+ if (cmd.build) {
+ options.build = true;
+ }
+
+ await watchDeps(options);
if (args?.length) {
await waitForExit(startChild(args));
diff --git a/packages/cli/src/helpers/paths.ts b/packages/cli/src/helpers/paths.ts
index 746b3f6ea8..e7b351ec16 100644
--- a/packages/cli/src/helpers/paths.ts
+++ b/packages/cli/src/helpers/paths.ts
@@ -25,6 +25,9 @@ export type Paths = {
// Root dir of the cli itself, containing package.json
ownDir: string;
+ // Monorepo root dir of the cli itself. Only accessible when running inside Backstage repo.
+ ownRoot: string;
+
// The location of the app that the cli is being executed in
targetDir: string;
@@ -34,6 +37,9 @@ export type Paths = {
// Resolve a path relative to own repo
resolveOwn: ResolveFunc;
+ // Resolve a path relative to own monorepo root. Only accessible when running inside Backstage repo.
+ resolveOwnRoot: ResolveFunc;
+
// Resolve a path relative to the app
resolveTarget: ResolveFunc;
@@ -53,7 +59,7 @@ export function findRootPath(topPath: string): string {
try {
const contents = fs.readFileSync(packagePath, 'utf8');
const data = JSON.parse(contents);
- if (data.name === 'root') {
+ if (data.name === 'root' || data.name.includes('backstage-e2e')) {
return path;
}
} catch (error) {
@@ -91,10 +97,31 @@ export function findOwnDir() {
return resolvePath(__dirname, path);
}
+// Finds the root of the monorepo that the cli exists in. Only accessible when running inside Backstage repo.
+export function findOwnRootPath(ownDir: string) {
+ const isLocal = fs.pathExistsSync(resolvePath(ownDir, 'src'));
+ if (!isLocal) {
+ throw new Error(
+ 'Tried to access monorepo package root dir outside of Backstage repository',
+ );
+ }
+
+ return resolvePath(ownDir, '../..');
+}
+
export function findPaths(): Paths {
const ownDir = findOwnDir();
const targetDir = fs.realpathSync(process.cwd());
+ // Lazy load this as it will throw an error if we're not inside the Backstage repo.
+ let ownRoot = '';
+ const getOwnRoot = () => {
+ if (!ownRoot) {
+ ownRoot = findOwnRootPath(ownDir);
+ }
+ return ownRoot;
+ };
+
// We're not always running in a monorepo, so we lazy init this to only crash commands
// that require a monorepo when we're not in one.
let targetRoot = '';
@@ -107,11 +134,15 @@ export function findPaths(): Paths {
return {
ownDir,
+ get ownRoot() {
+ return getOwnRoot();
+ },
targetDir,
get targetRoot() {
return getTargetRoot();
},
resolveOwn: (...paths) => resolvePath(ownDir, ...paths),
+ resolveOwnRoot: (...paths) => resolvePath(getOwnRoot(), ...paths),
resolveTarget: (...paths) => resolvePath(targetDir, ...paths),
resolveTargetRoot: (...paths) => resolvePath(getTargetRoot(), ...paths),
};
diff --git a/packages/cli/src/helpers/tasks.ts b/packages/cli/src/helpers/tasks.ts
index 3cba4d262f..0753301b78 100644
--- a/packages/cli/src/helpers/tasks.ts
+++ b/packages/cli/src/helpers/tasks.ts
@@ -37,6 +37,10 @@ export class Task {
process.stdout.write(`\n ${title}\n`);
}
+ static exit(code: number = 0) {
+ process.exit(code);
+ }
+
static async forItem(
task: string,
item: string,
diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts
index ca39c1ac3b..3ad43faa76 100644
--- a/packages/cli/src/index.ts
+++ b/packages/cli/src/index.ts
@@ -44,6 +44,13 @@ const main = (argv: string[]) => {
actionHandler(() => require('commands/create-plugin/createPlugin')),
);
+ program
+ .command('remove-plugin')
+ .description('Removes plugin in the current repository')
+ .action(
+ actionHandler(() => require('commands/remove-plugin/removePlugin')),
+ );
+
program
.command('plugin:build')
.option('--watch', 'Enable watch mode')
@@ -70,6 +77,7 @@ const main = (argv: string[]) => {
program
.command('watch-deps')
+ .option('--build', 'Build all dependencies on startup')
.description('Watch all dependencies while running another command')
.action(actionHandler(() => require('commands/watch-deps')));
@@ -90,6 +98,11 @@ const main = (argv: string[]) => {
)
.action(actionHandler(() => require('commands/build-cache')));
+ program
+ .command('clean')
+ .description('Delete cache directories')
+ .action(actionHandler(() => require('commands/clean/clean')));
+
program.on('command:*', () => {
console.log();
console.log(
diff --git a/packages/cli/templates/default-app/package.json.hbs b/packages/cli/templates/default-app/package.json.hbs
index 7b5fab5221..231194a54e 100644
--- a/packages/cli/templates/default-app/package.json.hbs
+++ b/packages/cli/templates/default-app/package.json.hbs
@@ -13,7 +13,8 @@
"test:all": "yarn build && lerna run test -- --coverage",
"lint": "lerna run lint --since origin/master --",
"lint:all": "lerna run lint --",
- "create-plugin": "backstage-cli create-plugin"
+ "create-plugin": "backstage-cli create-plugin",
+ "clean": "lerna run clean"
},
"workspaces": {
"packages": [
diff --git a/packages/cli/templates/default-app/packages/app/package.json.hbs b/packages/cli/templates/default-app/packages/app/package.json.hbs
index a5b2b07b96..940091380a 100644
--- a/packages/cli/templates/default-app/packages/app/package.json.hbs
+++ b/packages/cli/templates/default-app/packages/app/package.json.hbs
@@ -5,6 +5,7 @@
"dependencies": {
"@material-ui/core": "^4.9.1",
"@material-ui/icons": "^4.9.1",
+ "@material-ui/lab": "4.0.0-alpha.45",
"@backstage/cli": "^{{version}}",
"@backstage/core": "^{{version}}",
"@backstage/theme": "^{{version}}",
@@ -17,7 +18,8 @@
"plugin-welcome": "0.0.0",
"react": "^16.12.0",
"react-dom": "^16.12.0",
- "react-router-dom": "^5.1.2"
+ "react-router-dom": "^5.1.2",
+ "react-use": "^13.24.0"
},
"scripts": {
"start": "backstage-cli app:serve",
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 ec8d8d435a..5fa952239a 100644
--- a/packages/cli/templates/default-app/packages/app/src/App.tsx
+++ b/packages/cli/templates/default-app/packages/app/src/App.tsx
@@ -1,6 +1,6 @@
import { CssBaseline, makeStyles, ThemeProvider } from '@material-ui/core';
import { createApp } from '@backstage/core';
-import { BackstageTheme } from '@backstage/theme';
+import { lightTheme } from '@backstage/theme';
import React, { FC } from 'react';
import { BrowserRouter as Router } from 'react-router-dom';
import * as plugins from './plugins';
@@ -31,7 +31,7 @@ const App: FC<{}> = () => {
useStyles();
return (
-
+
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 6d9268fadd..27e44a3f75 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,12 +2,12 @@ import React from 'react';
import { render } from '@testing-library/react';
import WelcomePage from './WelcomePage';
import { ThemeProvider } from '@material-ui/core';
-import { BackstageTheme } from '@backstage/theme';
+import { lightTheme } from '@backstage/theme';
describe('WelcomePage', () => {
it('should render', () => {
const rendered = render(
-
+
,
);
diff --git a/packages/cli/templates/default-plugin/package.json.hbs b/packages/cli/templates/default-plugin/package.json.hbs
index 49d8d20d71..32479b879b 100644
--- a/packages/cli/templates/default-plugin/package.json.hbs
+++ b/packages/cli/templates/default-plugin/package.json.hbs
@@ -8,7 +8,8 @@
"scripts": {
"build": "backstage-cli plugin:build",
"lint": "backstage-cli lint",
- "test": "backstage-cli test"
+ "test": "backstage-cli test",
+ "clean": "backstage-cli clean"
},
"devDependencies": {
"@backstage/cli": "^{{version}}",
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 80b97169bf..dff57e66a4 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,13 +19,13 @@ 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/theme';
+import { lightTheme } from '@backstage/theme';
describe('ExampleComponent', () => {
it('should render', () => {
mockFetch.mockResponse(() => new Promise(() => {}));
const rendered = render(
-
+
,
);
diff --git a/packages/core/package.json b/packages/core/package.json
index d60dcf91be..418bac8da9 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.3",
+ "version": "0.1.1-alpha.4",
"private": false,
"publishConfig": {
"access": "public"
@@ -21,7 +21,8 @@
"scripts": {
"build": "backstage-cli plugin:build",
"lint": "backstage-cli lint",
- "test": "backstage-cli test"
+ "test": "backstage-cli test",
+ "clean": "backstage-cli clean"
},
"dependencies": {
"@material-ui/core": "^4.9.1",
@@ -39,16 +40,18 @@
"react-dom": "^16.12.0",
"react-helmet": "5.2.1",
"react-router-dom": "^5.1.2",
+ "react-sparklines": "^1.7.0",
"recompose": "0.30.0"
},
"devDependencies": {
- "@backstage/cli": "^0.1.1-alpha.3",
- "@backstage/test-utils-core": "^0.1.1-alpha.3",
- "@backstage/theme": "^0.1.1-alpha.3",
- "@storybook/addon-storysource": "^5.3.18",
+ "@backstage/cli": "^0.1.1-alpha.4",
+ "@backstage/test-utils": "0.1.1-alpha.4",
+ "@backstage/test-utils-core": "^0.1.1-alpha.4",
+ "@backstage/theme": "^0.1.1-alpha.4",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
+ "@types/react-sparklines": "^1.7.0",
"react-router": "^5.1.2"
},
"peerDependencies": {
diff --git a/packages/core/src/components/Lifecycle/LifecycleAlpha.stories.tsx b/packages/core/src/components/Lifecycle/LifecycleAlpha.stories.tsx
index 1b044fcd5f..072fb90a55 100644
--- a/packages/core/src/components/Lifecycle/LifecycleAlpha.stories.tsx
+++ b/packages/core/src/components/Lifecycle/LifecycleAlpha.stories.tsx
@@ -17,7 +17,7 @@ import React from 'react';
import { AlphaLabel } from './Lifecycle';
export default {
- title: 'Alpha Lifecycle',
+ title: 'Lifecycle - Alpha',
component: AlphaLabel,
};
diff --git a/packages/core/src/components/Lifecycle/LifecycleBeta.stories.tsx b/packages/core/src/components/Lifecycle/LifecycleBeta.stories.tsx
index b74171b5ea..f8cce2a562 100644
--- a/packages/core/src/components/Lifecycle/LifecycleBeta.stories.tsx
+++ b/packages/core/src/components/Lifecycle/LifecycleBeta.stories.tsx
@@ -17,7 +17,7 @@ import React from 'react';
import { BetaLabel } from './Lifecycle';
export default {
- title: 'Beta Lifecycle',
+ title: 'Lifecycle - Beta',
component: BetaLabel,
};
diff --git a/packages/core/src/components/ProgressBars/CircleProgress.tsx b/packages/core/src/components/ProgressBars/CircleProgress.tsx
index 4d9c5c3340..9290132eec 100644
--- a/packages/core/src/components/ProgressBars/CircleProgress.tsx
+++ b/packages/core/src/components/ProgressBars/CircleProgress.tsx
@@ -19,7 +19,7 @@ import { BackstageTheme } from '@backstage/theme';
import { Circle } from 'rc-progress';
import React, { FC } from 'react';
-const useStyles = makeStyles(theme => ({
+const useStyles = makeStyles(theme => ({
root: {
position: 'relative',
lineHeight: 0,
diff --git a/packages/core/src/components/Status/Status.tsx b/packages/core/src/components/Status/Status.tsx
index d03dc52b6e..0d02d86fb1 100644
--- a/packages/core/src/components/Status/Status.tsx
+++ b/packages/core/src/components/Status/Status.tsx
@@ -19,7 +19,7 @@ import { BackstageTheme } from '@backstage/theme';
import classNames from 'classnames';
import React, { FC } from 'react';
-const useStyles = makeStyles(theme => ({
+const useStyles = makeStyles(theme => ({
status: {
width: 12,
height: 12,
diff --git a/packages/core/src/components/TrendLine/TrendLine.stories.tsx b/packages/core/src/components/TrendLine/TrendLine.stories.tsx
new file mode 100644
index 0000000000..855b14207c
--- /dev/null
+++ b/packages/core/src/components/TrendLine/TrendLine.stories.tsx
@@ -0,0 +1,43 @@
+/*
+ * 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 TrendLine from '.';
+
+export default {
+ title: 'TrendLine',
+ component: TrendLine,
+};
+
+const width = 140;
+
+export const Default = () => (
+
+
+
+);
+
+export const TrendingUp = () => (
+
+
+
+);
+
+export const TrendingDown = () => (
+
+
+
+);
diff --git a/plugins/lighthouse/src/components/CategoryTrendline/index.test.tsx b/packages/core/src/components/TrendLine/TrendLine.test.tsx
similarity index 77%
rename from plugins/lighthouse/src/components/CategoryTrendline/index.test.tsx
rename to packages/core/src/components/TrendLine/TrendLine.test.tsx
index c45db58b9e..985e5d2b31 100644
--- a/plugins/lighthouse/src/components/CategoryTrendline/index.test.tsx
+++ b/packages/core/src/components/TrendLine/TrendLine.test.tsx
@@ -15,18 +15,17 @@
*/
/* eslint-disable jest/no-disabled-tests */
-
import React from 'react';
import { render } from '@testing-library/react';
import { wrapInThemedTestApp } from '@backstage/test-utils';
-import CategoryTrendline from '.';
+import TrendLine from '.';
-describe('CategoryTrendline', () => {
+describe('TrendLine', () => {
describe('when no data is present', () => {
it('renders null without throwing', () => {
const rendered = render(
- wrapInThemedTestApp(),
+ wrapInThemedTestApp(),
);
expect(rendered.queryByTitle('sparkline')).not.toBeInTheDocument();
});
@@ -35,9 +34,7 @@ describe('CategoryTrendline', () => {
describe('when one datapoint is present', () => {
it('renders as a straight line', () => {
const rendered = render(
- wrapInThemedTestApp(
- ,
- ),
+ wrapInThemedTestApp(),
);
expect(rendered.getByTitle('sparkline')).toBeInTheDocument();
});
@@ -46,9 +43,7 @@ describe('CategoryTrendline', () => {
describe.skip('when the data finishes above the success threshold', () => {
it('renders with the correct color', () => {
const rendered = render(
- wrapInThemedTestApp(
- ,
- ),
+ wrapInThemedTestApp(),
);
expect(rendered.getByTitle('sparkline')).toBeInTheDocument();
});
@@ -57,9 +52,7 @@ describe('CategoryTrendline', () => {
describe.skip('when the data finishes within the the warning threshold', () => {
it('renders with the correct color', () => {
const rendered = render(
- wrapInThemedTestApp(
- ,
- ),
+ wrapInThemedTestApp(),
);
expect(rendered.getByTitle('sparkline')).toBeInTheDocument();
});
@@ -68,9 +61,7 @@ describe('CategoryTrendline', () => {
describe.skip('when the data finishes within the the error threshold', () => {
it('renders with the correct color', () => {
const rendered = render(
- wrapInThemedTestApp(
- ,
- ),
+ wrapInThemedTestApp(),
);
expect(rendered.getByTitle('sparkline')).toBeInTheDocument();
});
diff --git a/plugins/lighthouse/src/components/CategoryTrendline/index.tsx b/packages/core/src/components/TrendLine/TrendLine.tsx
similarity index 83%
rename from plugins/lighthouse/src/components/CategoryTrendline/index.tsx
rename to packages/core/src/components/TrendLine/TrendLine.tsx
index a46aff52e5..3ab9b67e53 100644
--- a/plugins/lighthouse/src/components/CategoryTrendline/index.tsx
+++ b/packages/core/src/components/TrendLine/TrendLine.tsx
@@ -13,15 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
import React, { FC } from 'react';
import { Sparklines, SparklinesLine, SparklinesProps } from 'react-sparklines';
import { useTheme } from '@material-ui/core';
import { BackstageTheme } from '@backstage/theme';
-function color(
- data: number[],
- theme: typeof BackstageTheme,
-): string | undefined {
+function color(data: number[], theme: BackstageTheme): string | undefined {
const lastNum = data[data.length - 1];
if (!lastNum) return undefined;
if (lastNum >= 0.9) return theme.palette.status.ok;
@@ -29,8 +27,8 @@ function color(
return theme.palette.status.error;
}
-const CategoryTrendline: FC = props => {
- const theme = useTheme();
+const Trendline: FC = props => {
+ const theme = useTheme();
if (!props.data) return null;
return (
@@ -41,4 +39,4 @@ const CategoryTrendline: FC = props => {
);
};
-export default CategoryTrendline;
+export default Trendline;
diff --git a/packages/core/src/components/TrendLine/index.ts b/packages/core/src/components/TrendLine/index.ts
new file mode 100644
index 0000000000..168c6e6d3f
--- /dev/null
+++ b/packages/core/src/components/TrendLine/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { default } from './TrendLine';
diff --git a/packages/core/src/components/WarningPanel/WarningPanel.test.js b/packages/core/src/components/WarningPanel/WarningPanel.test.tsx
similarity index 100%
rename from packages/core/src/components/WarningPanel/WarningPanel.test.js
rename to packages/core/src/components/WarningPanel/WarningPanel.test.tsx
diff --git a/packages/core/src/components/WarningPanel/WarningPanel.js b/packages/core/src/components/WarningPanel/WarningPanel.tsx
similarity index 64%
rename from packages/core/src/components/WarningPanel/WarningPanel.js
rename to packages/core/src/components/WarningPanel/WarningPanel.tsx
index 679f7511d8..1d3b3e03db 100644
--- a/packages/core/src/components/WarningPanel/WarningPanel.js
+++ b/packages/core/src/components/WarningPanel/WarningPanel.tsx
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-import React, { Component } from 'react';
-import PropTypes from 'prop-types';
-import { Typography, withStyles } from '@material-ui/core';
+import React, { FC } from 'react';
+import { Typography, withStyles, makeStyles } from '@material-ui/core';
+import { BackstageTheme } from '@backstage/theme';
import ErrorOutline from '@material-ui/icons/ErrorOutline';
const errorOutlineStyles = theme => ({
@@ -27,7 +27,7 @@ const errorOutlineStyles = theme => ({
});
const ErrorOutlineStyled = withStyles(errorOutlineStyles)(ErrorOutline);
-const styles = theme => ({
+const useStyles = makeStyles(theme => ({
message: {
display: 'flex',
flexDirection: 'column',
@@ -47,34 +47,35 @@ const styles = theme => ({
messageText: {
color: theme.palette.warningText,
},
-});
+}));
/**
* WarningPanel. Show a user friendly error message to a user similar to ErrorPanel except that the warning panel
* only shows the warning message to the user
*/
-class WarningPanel extends Component {
- static propTypes = {
- message: PropTypes.node.isRequired,
- };
- render() {
- const { classes, title, message, children } = this.props;
- return (
-
-
-
-
- {title}
-
-
- {message && (
-
{message}
- )}
- {children}
+type Props = {
+ message?: React.ReactNode;
+ title?: string;
+};
+
+const WarningPanel: FC
= props => {
+ const classes = useStyles(props);
+ const { title, message, children } = props;
+ return (
+
+
+
+
+ {title}
+
- );
- }
-}
+ {message && (
+
{message}
+ )}
+ {children}
+
+ );
+};
-export default withStyles(styles)(WarningPanel);
+export default WarningPanel;
diff --git a/packages/core/src/components/WarningPanel/index.js b/packages/core/src/components/WarningPanel/index.ts
similarity index 100%
rename from packages/core/src/components/WarningPanel/index.js
rename to packages/core/src/components/WarningPanel/index.ts
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index 93288b9b68..af5789ef92 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -34,6 +34,7 @@ export { AlphaLabel, BetaLabel } from './components/Lifecycle';
export { default as SupportButton } from './components/SupportButton';
export { default as SortableTable } from './components/SortableTable';
export { default as StructuredMetadataTable } from './components/StructuredMetadataTable';
+export { default as TrendLine } from './components/TrendLine';
export { FeatureCalloutCircular } from './components/FeatureDiscovery/FeatureCalloutCircular';
export * from './components/Status';
export { default as WarningPanel } from './components/WarningPanel';
diff --git a/packages/core/src/layout/Header/Header.tsx b/packages/core/src/layout/Header/Header.tsx
index 5ed929d9a0..94de84ad8c 100644
--- a/packages/core/src/layout/Header/Header.tsx
+++ b/packages/core/src/layout/Header/Header.tsx
@@ -23,7 +23,7 @@ import { Theme } from 'layout/Page/Page';
// import { Link } from 'shared/components';
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/Sidebar/Bar.tsx b/packages/core/src/layout/Sidebar/Bar.tsx
index 4cf3db63ab..b32101a270 100644
--- a/packages/core/src/layout/Sidebar/Bar.tsx
+++ b/packages/core/src/layout/Sidebar/Bar.tsx
@@ -20,7 +20,7 @@ import React, { FC, useRef, useState } from 'react';
import { sidebarConfig, SidebarContext } from './config';
import { BackstageTheme } from '@backstage/theme';
-const useStyles = makeStyles(theme => ({
+const useStyles = makeStyles(theme => ({
root: {
zIndex: 1000,
position: 'relative',
diff --git a/packages/core/src/layout/TabbedCard/TabbedCard.stories.tsx b/packages/core/src/layout/TabbedCard/TabbedCard.stories.tsx
index d670b0b908..cf9fa6e6c6 100644
--- a/packages/core/src/layout/TabbedCard/TabbedCard.stories.tsx
+++ b/packages/core/src/layout/TabbedCard/TabbedCard.stories.tsx
@@ -15,19 +15,37 @@
*/
import React, { useState } from 'react';
import { TabbedCard, CardTab } from '.';
+import { Grid } from '@material-ui/core';
+
+const cardContentStyle = { height: 200, width: 500 };
export default {
title: 'Tabbed Card',
component: TabbedCard,
+ decorators: [
+ storyFn => (
+
+ {storyFn()}
+
+ ),
+ ],
};
export const Default = () => {
return (
- some content 1
- some content 2
- some content 3
- some content 4
+
+ Some content
+
+
+ Some content 2
+
+
+ Some content 3
+
+
+ Some content 4
+
);
};
@@ -37,10 +55,18 @@ const linkInfo = { title: 'Go to XYZ Location', link: '#' };
export const WithFooterLink = () => {
return (
- some content 1
- some content 2
- some content 3
- some content 4
+
+ Some content
+
+
+ Some content 2
+
+
+ Some content 3
+
+
+ Some content 4
+
);
};
@@ -60,16 +86,16 @@ export const WithControlledTabValue = () => {
title="Controlled Value Example"
>
- some content 1
+ Some content
- some content 2
+ Some content 2
- some content 3
+ Some content 3
- some content 4
+ Some content 4
>
diff --git a/packages/storybook/.storybook/config.js b/packages/storybook/.storybook/config.js
index b083bc0e9e..b9962b5bd3 100644
--- a/packages/storybook/.storybook/config.js
+++ b/packages/storybook/.storybook/config.js
@@ -1,10 +1,10 @@
import React from 'react';
import { addDecorator } from '@storybook/react';
-import { BackstageTheme } from '@backstage/theme';
+import { lightTheme } from '@backstage/theme';
import { CssBaseline, ThemeProvider } from '@material-ui/core';
addDecorator(story => (
-
+
{story()}
));
diff --git a/packages/storybook/.storybook/main.js b/packages/storybook/.storybook/main.js
index f086fa0d4a..530cd161ec 100644
--- a/packages/storybook/.storybook/main.js
+++ b/packages/storybook/.storybook/main.js
@@ -13,7 +13,7 @@ module.exports = {
webpackFinal: async config => {
config.resolve.alias = {
...config.resolve.alias,
- '@backstage/theme': path.resolve(__dirname, '../../theme/src'),
+ '@backstage/theme': path.resolve(__dirname, '../../theme'),
};
config.resolve.modules.push(path.resolve(__dirname, '../../core/src'));
config.module.rules.push(
diff --git a/packages/storybook/README.md b/packages/storybook/README.md
index 2e716a5466..66768c0fe5 100644
--- a/packages/storybook/README.md
+++ b/packages/storybook/README.md
@@ -1,3 +1,7 @@
# storybook
This package provides a storybook build for Backstage. See [storybook.backstage.io](http://storybook.backstage.io)
+
+## Why is this not part of `@backstage/core`?
+
+This separate storybook package exists because of dependency conflicts with `@backstage/cli`. It uses nohoist to avoid the conflicts, and since you can only use that in private packages it has to be separated out of `@backstage/core`.
diff --git a/packages/storybook/package.json b/packages/storybook/package.json
index f884da5f07..56ac5c6264 100644
--- a/packages/storybook/package.json
+++ b/packages/storybook/package.json
@@ -1,17 +1,21 @@
{
"name": "storybook",
- "version": "0.1.1-alpha.3",
+ "version": "0.1.1-alpha.4",
"description": "Storybook build for core package",
"private": true,
"scripts": {
- "start": "start-storybook -p 6006",
- "build-storybook": "build-storybook --output-dir dist"
+ "start": "backstage-cli watch-deps --build -- start-storybook -p 6006",
+ "build-storybook": "backstage-cli watch-deps --build -- build-storybook --output-dir dist"
},
"workspaces": {
"nohoist": [
- "@storybook/**"
+ "@storybook/react/**",
+ "@storybook/addons/**"
]
},
+ "dependencies": {
+ "@backstage/theme": "0.1.1-alpha.4"
+ },
"devDependencies": {
"@storybook/addon-actions": "^5.3.17",
"@storybook/addon-links": "^5.3.17",
diff --git a/packages/test-utils-core/package.json b/packages/test-utils-core/package.json
index 79d36a3525..569622ed1c 100644
--- a/packages/test-utils-core/package.json
+++ b/packages/test-utils-core/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/test-utils-core",
"description": "Utilities to test Backstage core",
- "version": "0.1.1-alpha.3",
+ "version": "0.1.1-alpha.4",
"private": false,
"publishConfig": {
"access": "public"
diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json
index ab52ca94a1..9eca38eeb5 100644
--- a/packages/test-utils/package.json
+++ b/packages/test-utils/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/test-utils",
"description": "Utilities to test Backstage plugins and apps.",
- "version": "0.1.1-alpha.3",
+ "version": "0.1.1-alpha.4",
"private": false,
"publishConfig": {
"access": "public"
@@ -21,11 +21,12 @@
"scripts": {
"build": "backstage-cli plugin:build",
"lint": "backstage-cli lint",
- "test": "backstage-cli test"
+ "test": "backstage-cli test",
+ "clean": "backstage-cli clean"
},
"devDependencies": {
- "@backstage/cli": "^0.1.1-alpha.3",
- "@backstage/theme": "^0.1.1-alpha.3",
+ "@backstage/cli": "^0.1.1-alpha.4",
+ "@backstage/theme": "^0.1.1-alpha.4",
"@material-ui/core": "^4.9.1",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
@@ -38,8 +39,8 @@
"react-router-dom": "^5.1.2"
},
"peerDependencies": {
- "@backstage/theme": "^0.1.1-alpha.3",
"@backstage/test-utils-core": "^0.1.1-alpha.3",
+ "@backstage/theme": "^0.1.1-alpha.3",
"@material-ui/core": "^4.9.1",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
diff --git a/packages/test-utils/src/testUtils/appWrappers.tsx b/packages/test-utils/src/testUtils/appWrappers.tsx
index 93f92ec537..06d8781669 100644
--- a/packages/test-utils/src/testUtils/appWrappers.tsx
+++ b/packages/test-utils/src/testUtils/appWrappers.tsx
@@ -18,7 +18,7 @@ import React, { ComponentType, ReactNode, FunctionComponent } from 'react';
import { ThemeProvider } from '@material-ui/core';
import { MemoryRouter } from 'react-router';
import { Route } from 'react-router-dom';
-import { BackstageTheme } from '@backstage/theme';
+import { lightTheme } from '@backstage/theme';
export function wrapInTestApp(
Component: ComponentType | ReactNode,
@@ -42,12 +42,10 @@ export function wrapInThemedTestApp(
component: ReactNode,
initialRouterEntries: string[] = ['/'],
) {
- const themed = (
- {component}
- );
+ const themed = {component};
return wrapInTestApp(themed, initialRouterEntries);
}
-export const wrapInTheme = (component: ReactNode, theme = BackstageTheme) => (
+export const wrapInTheme = (component: ReactNode, theme = lightTheme) => (
{component}
);
diff --git a/packages/theme/package.json b/packages/theme/package.json
index b08014e9d6..6504d7dea5 100644
--- a/packages/theme/package.json
+++ b/packages/theme/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/theme",
"description": "material-ui theme for use with Backstage.",
- "version": "0.1.1-alpha.3",
+ "version": "0.1.1-alpha.4",
"private": false,
"publishConfig": {
"access": "public"
@@ -20,10 +20,11 @@
"types": "dist/index.d.ts",
"scripts": {
"build": "backstage-cli plugin:build",
- "lint": "backstage-cli lint"
+ "lint": "backstage-cli lint",
+ "clean": "backstage-cli clean"
},
"devDependencies": {
- "@backstage/cli": "^0.1.1-alpha.3",
+ "@backstage/cli": "^0.1.1-alpha.4",
"@material-ui/core": "^4.9.1"
},
"peerDependencies": {
diff --git a/packages/theme/src/BackstageTheme.ts b/packages/theme/src/BackstageTheme.ts
deleted file mode 100644
index 4f61437400..0000000000
--- a/packages/theme/src/BackstageTheme.ts
+++ /dev/null
@@ -1,260 +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 { 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';
-
-const COLORS = {
- PAGE_BACKGROUND: '#F8F8F8',
- DEFAULT_PAGE_THEME_COLOR: '#7C3699',
- DEFAULT_PAGE_THEME_LIGHT_COLOR: '#ECDBF2',
- SIDEBAR_BACKGROUND_COLOR: '#171717',
- ERROR_BACKGROUND_COLOR: '#FFEBEE',
- ERROR_TEXT_COLOR: '#CA001B',
- INFO_TEXT_COLOR: '#004e8a',
- LINK_TEXT: '#0A6EBE',
- LINK_TEXT_HOVER: '#2196F3',
- NAMED: {
- WHITE: '#FEFEFE',
- },
- STATUS: {
- OK: '#1db855',
- WARNING: '#f49b20',
- ERROR: '#CA001B',
- },
-};
-
-const extendedThemeConfig: BackstageMuiThemeOptions = {
- props: {
- MuiGrid: {
- spacing: 2,
- },
- MuiSwitch: {
- color: 'primary',
- },
- },
- palette: {
- background: {
- default: COLORS.PAGE_BACKGROUND,
- // @ts-ignore
- informational: '#60a3cb',
- },
- status: {
- ok: COLORS.STATUS.OK,
- warning: COLORS.STATUS.WARNING,
- error: COLORS.STATUS.ERROR,
- running: '#BEBEBE',
- pending: '#5BC0DE',
- background: COLORS.NAMED.WHITE,
- },
- bursts: {
- fontColor: COLORS.NAMED.WHITE,
- slackChannelText: '#ddd',
- backgroundColor: {
- default: COLORS.DEFAULT_PAGE_THEME_COLOR,
- },
- },
- // @ts-ignore
- primary: {
- main: blue[500],
- },
- border: '#E6E6E6',
- textVerySubtle: '#DDD',
- textSubtle: '#6E6E6E',
- highlight: '#FFFBCC',
- errorBackground: COLORS.ERROR_BACKGROUND_COLOR,
- warningBackground: '#F59B23',
- infoBackground: '#ebf5ff',
- errorText: COLORS.ERROR_TEXT_COLOR,
- infoText: COLORS.INFO_TEXT_COLOR,
- warningText: COLORS.NAMED.WHITE,
- linkHover: COLORS.LINK_TEXT_HOVER,
- link: COLORS.LINK_TEXT,
- gold: yellow.A700,
- sidebar: COLORS.SIDEBAR_BACKGROUND_COLOR,
- },
- navigation: {
- width: 220,
- background: '#333333',
- },
- typography: {
- fontFamily: '"Helvetica Neue", Helvetica, Roboto, Arial, sans-serif',
- h5: {
- fontWeight: 700,
- },
- h4: {
- fontWeight: 700,
- fontSize: 28,
- marginBottom: 6,
- },
- h3: {
- fontSize: 32,
- fontWeight: 700,
- marginBottom: 6,
- },
- h2: {
- fontSize: 40,
- fontWeight: 700,
- marginBottom: 8,
- },
- h1: {
- fontSize: 54,
- fontWeight: 700,
- marginBottom: 10,
- },
- },
-};
-
-const createOverrides = (
- theme: BackstageMuiTheme,
-): Partial => {
- return {
- overrides: {
- MuiTableRow: {
- // Alternating row backgrounds
- root: {
- '&:nth-of-type(odd)': {
- backgroundColor: theme.palette.background.default,
- },
- },
- // Use pointer for hoverable rows
- hover: {
- '&:hover': {
- cursor: 'pointer',
- },
- },
- // Alternating head backgrounds
- head: {
- '&:nth-of-type(odd)': {
- backgroundColor: COLORS.NAMED.WHITE,
- },
- },
- },
- // Tables are more dense than default mui tables
- MuiTableCell: {
- root: {
- wordBreak: 'break-word',
- overflow: 'hidden',
- verticalAlign: 'middle',
- lineHeight: '1',
- margin: 0,
- padding: '8px',
- borderBottom: 0,
- },
- head: {
- wordBreak: 'break-word',
- overflow: 'hidden',
- color: 'rgb(179, 179, 179)',
- fontWeight: 'normal',
- lineHeight: '1',
- },
- },
- MuiTabs: {
- // Tabs are smaller than default mui tab rows
- root: {
- minHeight: 24,
- },
- },
- MuiTab: {
- // Tabs are smaller and have a hover background
- root: {
- color: theme.palette.link,
- minHeight: 24,
- textTransform: 'initial',
- '&:hover': {
- color: darken(theme.palette.link, 0.3),
- background: lighten(theme.palette.link, 0.95),
- },
- [theme.breakpoints.up('md')]: {
- minWidth: 120,
- fontSize: theme.typography.pxToRem(14),
- fontWeight: 500,
- },
- },
- textColorPrimary: {
- color: theme.palette.link,
- },
- },
- MuiTableSortLabel: {
- // No color change on hover, just rely on the arrow showing up instead.
- root: {
- color: 'inherit',
- '&:hover': {
- color: 'inherit',
- },
- '&:focus': {
- color: 'inherit',
- },
- },
- // Bold font for highlighting selected column
- active: {
- fontWeight: 'bold',
- color: 'inherit',
- },
- },
- MuiListItemText: {
- dense: {
- // Default dense list items to adding ellipsis for really long str...
- whiteSpace: 'nowrap',
- overflow: 'hidden',
- textOverflow: 'ellipsis',
- },
- },
- MuiButton: {
- text: {
- // Text buttons have less padding by default, but we want to keep the original padding
- padding: undefined,
- },
- },
- MuiChip: {
- root: {
- // By default there's no margin, but it's usually wanted, so we add some trailing margin
- marginRight: theme.spacing(1),
- marginBottom: theme.spacing(1),
- },
- },
- MuiCardHeader: {
- root: {
- // Reduce padding between header and content
- paddingBottom: 0,
- },
- },
- MuiCardActions: {
- root: {
- // We default to putting the card actions at the end
- justifyContent: 'flex-end',
- },
- },
- },
- };
-};
-
-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: BackstageMuiTheme = {
- ...extendedTheme,
- ...createOverrides(extendedTheme),
-};
-
-// Temporary workaround for files incorrectly importing the theme directly
-export const V1 = BackstageTheme;
-export default BackstageTheme;
diff --git a/packages/theme/src/BackstageThemeDark.ts b/packages/theme/src/BackstageThemeDark.ts
deleted file mode 100644
index 9952a5eeaa..0000000000
--- a/packages/theme/src/BackstageThemeDark.ts
+++ /dev/null
@@ -1,273 +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 { 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';
-
-const COLORS = {
- PAGE_BACKGROUND: '#282828',
- DEFAULT_PAGE_THEME_COLOR: '#7C3699',
- DEFAULT_PAGE_THEME_LIGHT_COLOR: '#ECDBF2',
- SIDEBAR_BACKGROUND_COLOR: '#424242',
- ERROR_BACKGROUND_COLOR: '#FFEBEE',
- ERROR_TEXT_COLOR: '#CA001B',
- INFO_TEXT_COLOR: '#004e8a',
- LINK_TEXT: '#0A6EBE',
- LINK_TEXT_HOVER: '#2196F3',
- NAMED: {
- WHITE: '#FEFEFE',
- },
- STATUS: {
- OK: '#1db855',
- WARNING: '#f49b20',
- ERROR: '#CA001B',
- },
-};
-
-const extendedThemeConfig: BackstageMuiThemeOptions = {
- props: {
- MuiGrid: {
- spacing: 2,
- },
- MuiSwitch: {
- color: 'primary',
- },
- },
- palette: {
- background: {
- default: COLORS.PAGE_BACKGROUND,
- // @ts-ignore
- informational: '#60a3cb',
- },
- color: {
- default: '#fff',
- },
- type: 'dark',
- status: {
- ok: COLORS.STATUS.OK,
- warning: COLORS.STATUS.WARNING,
- error: COLORS.STATUS.ERROR,
- running: '#BEBEBE',
- pending: '#5BC0DE',
- background: COLORS.NAMED.WHITE,
- },
- bursts: {
- fontColor: COLORS.NAMED.WHITE,
- slackChannelText: '#ddd',
- backgroundColor: {
- default: COLORS.DEFAULT_PAGE_THEME_COLOR,
- },
- },
- // @ts-ignore
- primary: {
- main: blue[500],
- },
- border: '#E6E6E6',
- textVerySubtle: '#DDD',
- textSubtle: '#6E6E6E',
- highlight: '#FFFBCC',
- errorBackground: COLORS.ERROR_BACKGROUND_COLOR,
- warningBackground: '#F59B23',
- infoBackground: '#ebf5ff',
- errorText: COLORS.ERROR_TEXT_COLOR,
- infoText: COLORS.INFO_TEXT_COLOR,
- warningText: COLORS.NAMED.WHITE,
- linkHover: COLORS.LINK_TEXT_HOVER,
- link: COLORS.LINK_TEXT,
- gold: yellow.A700,
- sidebar: COLORS.SIDEBAR_BACKGROUND_COLOR,
- },
- navigation: {
- width: 220,
- background: '#333333',
- },
- typography: {
- fontFamily: '"Helvetica Neue", Helvetica, Roboto, Arial, sans-serif',
- h5: {
- fontWeight: 700,
- },
- h4: {
- fontWeight: 700,
- fontSize: 28,
- marginBottom: 6,
- },
- h3: {
- fontSize: 32,
- fontWeight: 700,
- marginBottom: 6,
- },
- h2: {
- fontSize: 40,
- fontWeight: 700,
- marginBottom: 8,
- },
- h1: {
- fontSize: 54,
- fontWeight: 700,
- marginBottom: 10,
- },
- },
-};
-
-const createOverrides = (theme: BackstageMuiTheme): BackstageMuiTheme => {
- return {
- overrides: {
- // @ts-ignore
- MuiCSSBaseline: {
- '@global': {
- body: {
- backgroundColor: theme.palette.background.default,
- // @ts-ignore
- color: theme.palette.color.default,
- },
- },
- },
- MuiTableRow: {
- // Alternating row backgrounds
- root: {
- '&:nth-of-type(odd)': {
- backgroundColor: theme.palette.background.default,
- },
- },
- // Use pointer for hoverable rows
- hover: {
- '&:hover': {
- cursor: 'pointer',
- },
- },
- // Alternating head backgrounds
- head: {
- '&:nth-of-type(odd)': {
- backgroundColor: COLORS.NAMED.WHITE,
- },
- },
- },
- // Tables are more dense than default mui tables
- MuiTableCell: {
- root: {
- wordBreak: 'break-word',
- overflow: 'hidden',
- verticalAlign: 'middle',
- lineHeight: '1',
- margin: 0,
- padding: '8px',
- borderBottom: 0,
- },
- head: {
- wordBreak: 'break-word',
- overflow: 'hidden',
- color: 'rgb(179, 179, 179)',
- fontWeight: 'normal',
- lineHeight: '1',
- },
- },
- MuiTabs: {
- // Tabs are smaller than default mui tab rows
- root: {
- minHeight: 24,
- },
- },
- MuiTab: {
- // Tabs are smaller and have a hover background
- root: {
- color: theme.palette.link,
- minHeight: 24,
- textTransform: 'initial',
- '&:hover': {
- color: darken(theme.palette.link, 0.3),
- background: lighten(theme.palette.link, 0.95),
- },
- [theme.breakpoints.up('md')]: {
- minWidth: 120,
- fontSize: theme.typography.pxToRem(14),
- fontWeight: 500,
- },
- },
- textColorPrimary: {
- color: theme.palette.link,
- },
- },
- MuiTableSortLabel: {
- // No color change on hover, just rely on the arrow showing up instead.
- root: {
- color: 'inherit',
- '&:hover': {
- color: 'inherit',
- },
- '&:focus': {
- color: 'inherit',
- },
- },
- // Bold font for highlighting selected column
- active: {
- fontWeight: 'bold',
- color: 'inherit',
- },
- },
- MuiListItemText: {
- dense: {
- // Default dense list items to adding ellipsis for really long str...
- whiteSpace: 'nowrap',
- overflow: 'hidden',
- textOverflow: 'ellipsis',
- },
- },
- MuiButton: {
- text: {
- // Text buttons have less padding by default, but we want to keep the original padding
- padding: undefined,
- },
- },
- MuiChip: {
- root: {
- // By default there's no margin, but it's usually wanted, so we add some trailing margin
- marginRight: theme.spacing(1),
- marginBottom: theme.spacing(1),
- },
- },
- MuiCardHeader: {
- root: {
- // Reduce padding between header and content
- paddingBottom: 0,
- },
- },
- MuiCardActions: {
- root: {
- // We default to putting the card actions at the end
- justifyContent: 'flex-end',
- },
- },
- },
- };
-};
-
-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 BackstageThemeDark = {
- ...extendedTheme,
- ...createOverrides(extendedTheme),
-};
-
-// Temporary workaround for files incorrectly importing the theme directly
-export const V1 = BackstageThemeDark;
-
-export default BackstageThemeDark;
diff --git a/packages/theme/src/BackstageThemeLight.ts b/packages/theme/src/BackstageThemeLight.ts
deleted file mode 100644
index a0a063ad24..0000000000
--- a/packages/theme/src/BackstageThemeLight.ts
+++ /dev/null
@@ -1,274 +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 { 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';
-
-const COLORS = {
- PAGE_BACKGROUND: '#F8F8F8',
- DEFAULT_PAGE_THEME_COLOR: '#7C3699',
- DEFAULT_PAGE_THEME_LIGHT_COLOR: '#ECDBF2',
- SIDEBAR_BACKGROUND_COLOR: '#171717',
- ERROR_BACKGROUND_COLOR: '#FFEBEE',
- ERROR_TEXT_COLOR: '#CA001B',
- INFO_TEXT_COLOR: '#004e8a',
- LINK_TEXT: '#0A6EBE',
- LINK_TEXT_HOVER: '#2196F3',
- NAMED: {
- WHITE: '#FEFEFE',
- },
- STATUS: {
- OK: '#1db855',
- WARNING: '#f49b20',
- ERROR: '#CA001B',
- },
-};
-
-const extendedThemeConfig: BackstageMuiThemeOptions = {
- props: {
- MuiGrid: {
- spacing: 2,
- },
- MuiSwitch: {
- color: 'primary',
- },
- },
- palette: {
- background: {
- default: COLORS.PAGE_BACKGROUND,
- // @ts-ignore
- informational: '#60a3cb',
- },
- color: {
- default: '#000',
- },
- status: {
- ok: COLORS.STATUS.OK,
- warning: COLORS.STATUS.WARNING,
- error: COLORS.STATUS.ERROR,
- running: '#BEBEBE',
- pending: '#5BC0DE',
- background: COLORS.NAMED.WHITE,
- },
- bursts: {
- fontColor: COLORS.NAMED.WHITE,
- slackChannelText: '#ddd',
- backgroundColor: {
- default: COLORS.DEFAULT_PAGE_THEME_COLOR,
- },
- },
- // @ts-ignore
- primary: {
- main: blue[500],
- },
- border: '#E6E6E6',
- textVerySubtle: '#DDD',
- textSubtle: '#6E6E6E',
- highlight: '#FFFBCC',
- errorBackground: COLORS.ERROR_BACKGROUND_COLOR,
- warningBackground: '#F59B23',
- infoBackground: '#ebf5ff',
- errorText: COLORS.ERROR_TEXT_COLOR,
- infoText: COLORS.INFO_TEXT_COLOR,
- warningText: COLORS.NAMED.WHITE,
- linkHover: COLORS.LINK_TEXT_HOVER,
- link: COLORS.LINK_TEXT,
- gold: yellow.A700,
- sidebar: COLORS.SIDEBAR_BACKGROUND_COLOR,
- },
- navigation: {
- width: 220,
- background: '#333333',
- },
- typography: {
- fontFamily: '"Helvetica Neue", Helvetica, Roboto, Arial, sans-serif',
- h5: {
- fontWeight: 700,
- },
- h4: {
- fontWeight: 700,
- fontSize: 28,
- marginBottom: 6,
- },
- h3: {
- fontSize: 32,
- fontWeight: 700,
- marginBottom: 6,
- },
- h2: {
- fontSize: 40,
- fontWeight: 700,
- marginBottom: 8,
- },
- h1: {
- fontSize: 54,
- fontWeight: 700,
- marginBottom: 10,
- },
- },
-};
-
-const createOverrides = (
- theme: BackstageMuiTheme,
-): Partial => {
- return {
- overrides: {
- // @ts-ignore
- MuiCSSBaseline: {
- '@global': {
- body: {
- backgroundColor: theme.palette.background.default,
- // @ts-ignore
- color: theme.palette.color.default,
- },
- },
- },
- MuiTableRow: {
- // Alternating row backgrounds
- root: {
- '&:nth-of-type(odd)': {
- backgroundColor: theme.palette.background.default,
- },
- },
- // Use pointer for hoverable rows
- hover: {
- '&:hover': {
- cursor: 'pointer',
- },
- },
- // Alternating head backgrounds
- head: {
- '&:nth-of-type(odd)': {
- backgroundColor: COLORS.NAMED.WHITE,
- },
- },
- },
- // Tables are more dense than default mui tables
- MuiTableCell: {
- root: {
- wordBreak: 'break-word',
- overflow: 'hidden',
- verticalAlign: 'middle',
- lineHeight: '1',
- margin: 0,
- padding: '8px',
- borderBottom: 0,
- },
- head: {
- wordBreak: 'break-word',
- overflow: 'hidden',
- color: 'rgb(179, 179, 179)',
- fontWeight: 'normal',
- lineHeight: '1',
- },
- },
- MuiTabs: {
- // Tabs are smaller than default mui tab rows
- root: {
- minHeight: 24,
- },
- },
- MuiTab: {
- // Tabs are smaller and have a hover background
- root: {
- color: theme.palette.link,
- minHeight: 24,
- textTransform: 'initial',
- '&:hover': {
- color: darken(theme.palette.link, 0.3),
- background: lighten(theme.palette.link, 0.95),
- },
- [theme.breakpoints.up('md')]: {
- minWidth: 120,
- fontSize: theme.typography.pxToRem(14),
- fontWeight: 500,
- },
- },
- textColorPrimary: {
- color: theme.palette.link,
- },
- },
- MuiTableSortLabel: {
- // No color change on hover, just rely on the arrow showing up instead.
- root: {
- color: 'inherit',
- '&:hover': {
- color: 'inherit',
- },
- '&:focus': {
- color: 'inherit',
- },
- },
- // Bold font for highlighting selected column
- active: {
- fontWeight: 'bold',
- color: 'inherit',
- },
- },
- MuiListItemText: {
- dense: {
- // Default dense list items to adding ellipsis for really long str...
- whiteSpace: 'nowrap',
- overflow: 'hidden',
- textOverflow: 'ellipsis',
- },
- },
- MuiButton: {
- text: {
- // Text buttons have less padding by default, but we want to keep the original padding
- padding: undefined,
- },
- },
- MuiChip: {
- root: {
- // By default there's no margin, but it's usually wanted, so we add some trailing margin
- marginRight: theme.spacing(1),
- marginBottom: theme.spacing(1),
- },
- },
- MuiCardHeader: {
- root: {
- // Reduce padding between header and content
- paddingBottom: 0,
- },
- },
- MuiCardActions: {
- root: {
- // We default to putting the card actions at the end
- justifyContent: 'flex-end',
- },
- },
- },
- };
-};
-
-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 BackstageThemeLight = {
- ...extendedTheme,
- ...createOverrides(extendedTheme),
-};
-
-// Temporary workaround for files incorrectly importing the theme directly
-export const V1 = BackstageThemeLight;
-
-export default BackstageThemeLight;
diff --git a/packages/theme/src/baseTheme.ts b/packages/theme/src/baseTheme.ts
new file mode 100644
index 0000000000..da9b3299a3
--- /dev/null
+++ b/packages/theme/src/baseTheme.ts
@@ -0,0 +1,197 @@
+/*
+ * 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 { createMuiTheme } from '@material-ui/core';
+import { darken, lighten } from '@material-ui/core/styles/colorManipulator';
+import { Overrides } from '@material-ui/core/styles/overrides';
+
+import {
+ BackstageTheme,
+ BackstageThemeOptions,
+ BackstagePaletteOptions,
+} from './types';
+
+export function createThemeOptions(
+ palette: BackstagePaletteOptions,
+): BackstageThemeOptions {
+ return {
+ palette,
+ props: {
+ MuiGrid: {
+ spacing: 2,
+ },
+ MuiSwitch: {
+ color: 'primary',
+ },
+ },
+ typography: {
+ fontFamily: '"Helvetica Neue", Helvetica, Roboto, Arial, sans-serif',
+ h5: {
+ fontWeight: 700,
+ },
+ h4: {
+ fontWeight: 700,
+ fontSize: 28,
+ marginBottom: 6,
+ },
+ h3: {
+ fontSize: 32,
+ fontWeight: 700,
+ marginBottom: 6,
+ },
+ h2: {
+ fontSize: 40,
+ fontWeight: 700,
+ marginBottom: 8,
+ },
+ h1: {
+ fontSize: 54,
+ fontWeight: 700,
+ marginBottom: 10,
+ },
+ },
+ };
+}
+
+export function createThemeOverrides(theme: BackstageTheme): Overrides {
+ return {
+ MuiTableRow: {
+ // Alternating row backgrounds
+ root: {
+ '&:nth-of-type(odd)': {
+ backgroundColor: theme.palette.background.default,
+ },
+ },
+ // Use pointer for hoverable rows
+ hover: {
+ '&:hover': {
+ cursor: 'pointer',
+ },
+ },
+ // Alternating head backgrounds
+ head: {
+ '&:nth-of-type(odd)': {
+ backgroundColor: theme.palette.background.paper,
+ },
+ },
+ },
+ // Tables are more dense than default mui tables
+ MuiTableCell: {
+ root: {
+ wordBreak: 'break-word',
+ overflow: 'hidden',
+ verticalAlign: 'middle',
+ lineHeight: '1',
+ margin: 0,
+ padding: '8px',
+ borderBottom: 0,
+ },
+ head: {
+ wordBreak: 'break-word',
+ overflow: 'hidden',
+ color: 'rgb(179, 179, 179)',
+ fontWeight: 'normal',
+ lineHeight: '1',
+ },
+ },
+ MuiTabs: {
+ // Tabs are smaller than default mui tab rows
+ root: {
+ minHeight: 24,
+ },
+ },
+ MuiTab: {
+ // Tabs are smaller and have a hover background
+ root: {
+ color: theme.palette.link,
+ minHeight: 24,
+ textTransform: 'initial',
+ '&:hover': {
+ color: darken(theme.palette.link, 0.3),
+ background: lighten(theme.palette.link, 0.95),
+ },
+ [theme.breakpoints.up('md')]: {
+ minWidth: 120,
+ fontSize: theme.typography.pxToRem(14),
+ fontWeight: 500,
+ },
+ },
+ textColorPrimary: {
+ color: theme.palette.link,
+ },
+ },
+ MuiTableSortLabel: {
+ // No color change on hover, just rely on the arrow showing up instead.
+ root: {
+ color: 'inherit',
+ '&:hover': {
+ color: 'inherit',
+ },
+ '&:focus': {
+ color: 'inherit',
+ },
+ },
+ // Bold font for highlighting selected column
+ active: {
+ fontWeight: 'bold',
+ color: 'inherit',
+ },
+ },
+ MuiListItemText: {
+ dense: {
+ // Default dense list items to adding ellipsis for really long str...
+ whiteSpace: 'nowrap',
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ },
+ },
+ MuiButton: {
+ text: {
+ // Text buttons have less padding by default, but we want to keep the original padding
+ padding: undefined,
+ },
+ },
+ MuiChip: {
+ root: {
+ // By default there's no margin, but it's usually wanted, so we add some trailing margin
+ marginRight: theme.spacing(1),
+ marginBottom: theme.spacing(1),
+ },
+ },
+ MuiCardHeader: {
+ root: {
+ // Reduce padding between header and content
+ paddingBottom: 0,
+ },
+ },
+ MuiCardActions: {
+ root: {
+ // We default to putting the card actions at the end
+ justifyContent: 'flex-end',
+ },
+ },
+ };
+}
+
+// Creates a Backstage MUI theme using a palette.
+// The theme is created with the common Backstage options and component styles.
+export function createTheme(palette: BackstagePaletteOptions): BackstageTheme {
+ const themeOptions = createThemeOptions(palette);
+ const baseTheme = createMuiTheme(themeOptions) as BackstageTheme;
+ const overrides = createThemeOverrides(baseTheme);
+ const theme = { ...baseTheme, overrides };
+ return theme;
+}
diff --git a/packages/theme/src/index.ts b/packages/theme/src/index.ts
index 8aa7e30ef5..862f9b7755 100644
--- a/packages/theme/src/index.ts
+++ b/packages/theme/src/index.ts
@@ -13,6 +13,7 @@
* 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 } from './BackstageTheme';
+
+export * from './themes';
+export * from './baseTheme';
+export * from './types';
diff --git a/packages/theme/src/themes.ts b/packages/theme/src/themes.ts
new file mode 100644
index 0000000000..eb36a37bf5
--- /dev/null
+++ b/packages/theme/src/themes.ts
@@ -0,0 +1,96 @@
+/*
+ * 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 { createTheme } from 'baseTheme';
+import { blue, yellow } from '@material-ui/core/colors';
+
+export const lightTheme = createTheme({
+ type: 'light',
+ background: {
+ default: '#F8F8F8',
+ },
+ status: {
+ ok: '#1db855',
+ warning: '#f49b20',
+ error: '#CA001B',
+ running: '#BEBEBE',
+ pending: '#5BC0DE',
+ background: '#FEFEFE',
+ },
+ bursts: {
+ fontColor: '#FEFEFE',
+ slackChannelText: '#ddd',
+ backgroundColor: {
+ default: '#7C3699',
+ },
+ },
+ primary: {
+ main: blue[500],
+ },
+ border: '#E6E6E6',
+ textVerySubtle: '#DDD',
+ textSubtle: '#6E6E6E',
+ highlight: '#FFFBCC',
+ errorBackground: '#FFEBEE',
+ warningBackground: '#F59B23',
+ infoBackground: '#ebf5ff',
+ errorText: '#CA001B',
+ infoText: '#004e8a',
+ warningText: '#FEFEFE',
+ linkHover: '#2196F3',
+ link: '#0A6EBE',
+ gold: yellow.A700,
+ sidebar: '#171717',
+});
+
+export const darkTheme = createTheme({
+ type: 'dark',
+ background: {
+ default: '#282828',
+ },
+ status: {
+ ok: '#1db855',
+ warning: '#f49b20',
+ error: '#CA001B',
+ running: '#BEBEBE',
+ pending: '#5BC0DE',
+ background: '#FEFEFE',
+ },
+ bursts: {
+ fontColor: '#FEFEFE',
+ slackChannelText: '#ddd',
+ backgroundColor: {
+ default: '#7C3699',
+ },
+ },
+ primary: {
+ main: blue[500],
+ },
+ border: '#E6E6E6',
+ textVerySubtle: '#DDD',
+ textSubtle: '#6E6E6E',
+ highlight: '#FFFBCC',
+ errorBackground: '#FFEBEE',
+ warningBackground: '#F59B23',
+ infoBackground: '#ebf5ff',
+ errorText: '#CA001B',
+ infoText: '#004e8a',
+ warningText: '#FEFEFE',
+ linkHover: '#2196F3',
+ link: '#0A6EBE',
+ gold: yellow.A700,
+ sidebar: '#424242',
+});
diff --git a/packages/theme/src/types.ts b/packages/theme/src/types.ts
index cbd3de4efa..f0e43c54d1 100644
--- a/packages/theme/src/types.ts
+++ b/packages/theme/src/types.ts
@@ -15,8 +15,12 @@
*/
import { Theme, ThemeOptions } from '@material-ui/core';
+import {
+ PaletteOptions,
+ Palette,
+} from '@material-ui/core/styles/createPalette';
-export type BackstageMuiPalette = Theme['palette'] & {
+type PaletteAdditions = {
status: {
ok: string;
warning: string;
@@ -48,10 +52,13 @@ export type BackstageMuiPalette = Theme['palette'] & {
};
};
-export interface BackstageMuiTheme extends Theme {
- palette: BackstageMuiPalette;
+export type BackstagePalette = Palette & PaletteAdditions;
+export type BackstagePaletteOptions = PaletteOptions & PaletteAdditions;
+
+export interface BackstageTheme extends Theme {
+ palette: BackstagePalette;
}
-export interface BackstageMuiThemeOptions extends ThemeOptions {
- palette: Partial;
+export interface BackstageThemeOptions extends ThemeOptions {
+ palette: BackstagePaletteOptions;
}
diff --git a/plugins/home-page/package.json b/plugins/home-page/package.json
index 7f78f1bf63..65fb3cf9da 100644
--- a/plugins/home-page/package.json
+++ b/plugins/home-page/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-home-page",
- "version": "0.1.1-alpha.3",
+ "version": "0.1.1-alpha.4",
"main": "dist/index.cjs.js",
"types": "dist/index.d.ts",
"license": "Apache-2.0",
@@ -8,12 +8,13 @@
"scripts": {
"build": "backstage-cli plugin:build",
"lint": "backstage-cli lint",
- "test": "backstage-cli test"
+ "test": "backstage-cli test",
+ "clean": "backstage-cli clean"
},
"devDependencies": {
- "@backstage/cli": "^0.1.1-alpha.3",
- "@backstage/core": "^0.1.1-alpha.3",
- "@backstage/theme": "^0.1.1-alpha.3",
+ "@backstage/cli": "^0.1.1-alpha.4",
+ "@backstage/core": "^0.1.1-alpha.4",
+ "@backstage/theme": "^0.1.1-alpha.4",
"@material-ui/core": "^4.9.1",
"@material-ui/icons": "^4.9.1",
"@testing-library/jest-dom": "^4.2.4",
diff --git a/plugins/home-page/src/components/HomePage/HomePage.test.tsx b/plugins/home-page/src/components/HomePage/HomePage.test.tsx
index 713f0afe1e..e7df794a00 100644
--- a/plugins/home-page/src/components/HomePage/HomePage.test.tsx
+++ b/plugins/home-page/src/components/HomePage/HomePage.test.tsx
@@ -18,12 +18,12 @@ import React from 'react';
import { render } from '@testing-library/react';
import HomePage from './HomePage';
import { ThemeProvider } from '@material-ui/core';
-import { BackstageTheme } from '@backstage/theme';
+import { lightTheme } from '@backstage/theme';
describe('HomePage', () => {
it('should render', () => {
const rendered = render(
-
+
,
);
diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json
index 721f20f802..d082b0be0f 100644
--- a/plugins/lighthouse/package.json
+++ b/plugins/lighthouse/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-lighthouse",
- "version": "0.1.1-alpha.3",
+ "version": "0.1.1-alpha.4",
"main": "dist/index.cjs.js",
"types": "dist/index.d.ts",
"license": "Apache-2.0",
@@ -9,17 +9,17 @@
"build:watch": "backstage-cli plugin:build --watch",
"build": "backstage-cli plugin:build",
"lint": "backstage-cli lint",
- "test": "backstage-cli test"
+ "test": "backstage-cli test",
+ "clean": "backstage-cli clean"
},
"dependencies": {
- "react-markdown": "^4.3.1",
- "react-sparklines": "^1.7.0"
+ "react-markdown": "^4.3.1"
},
"devDependencies": {
- "@backstage/cli": "^0.1.1-alpha.3",
- "@backstage/core": "^0.1.1-alpha.3",
- "@backstage/test-utils": "^0.1.1-alpha.3",
- "@backstage/theme": "^0.1.1-alpha.3",
+ "@backstage/cli": "^0.1.1-alpha.4",
+ "@backstage/core": "^0.1.1-alpha.4",
+ "@backstage/test-utils": "^0.1.1-alpha.4",
+ "@backstage/theme": "^0.1.1-alpha.4",
"@material-ui/core": "^4.9.1",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -28,7 +28,6 @@
"@testing-library/user-event": "^7.1.2",
"@types/jest": "^24.0.0",
"@types/node": "^12.0.0",
- "@types/react-sparklines": "^1.7.0",
"@types/testing-library__jest-dom": "5.0.2",
"jest-fetch-mock": "^3.0.3",
"react": "^16.13.1",
diff --git a/plugins/lighthouse/src/components/AuditList/AuditListTable.tsx b/plugins/lighthouse/src/components/AuditList/AuditListTable.tsx
index 52f3eeb5e2..ba0e79f066 100644
--- a/plugins/lighthouse/src/components/AuditList/AuditListTable.tsx
+++ b/plugins/lighthouse/src/components/AuditList/AuditListTable.tsx
@@ -24,6 +24,7 @@ import {
TableRow,
} from '@material-ui/core';
import { makeStyles } from '@material-ui/core/styles';
+import { TrendLine } from '@backstage/core';
import {
Audit,
@@ -32,7 +33,6 @@ import {
Website,
} from '../../api';
import { formatTime } from '../../utils';
-import CategoryTrendline from '../CategoryTrendline';
import AuditStatusIcon from '../AuditStatusIcon';
export const CATEGORIES: LighthouseCategoryId[] = [
@@ -132,7 +132,7 @@ export const AuditListTable: FC<{ items: Website[] }> = ({ items }) => {
key={`${website.url}|${category}`}
className={classes.sparklinesCell}
>
-
diff --git a/plugins/welcome/package.json b/plugins/welcome/package.json
index 3ee95e8898..86cb0e3f45 100644
--- a/plugins/welcome/package.json
+++ b/plugins/welcome/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-welcome",
- "version": "0.1.1-alpha.3",
+ "version": "0.1.1-alpha.4",
"main": "dist/index.cjs.js",
"types": "dist/index.d.ts",
"private": true,
@@ -8,12 +8,13 @@
"scripts": {
"build": "backstage-cli plugin:build",
"lint": "backstage-cli lint",
- "test": "backstage-cli test"
+ "test": "backstage-cli test",
+ "clean": "backstage-cli clean"
},
"devDependencies": {
- "@backstage/cli": "^0.1.1-alpha.3",
- "@backstage/core": "^0.1.1-alpha.3",
- "@backstage/theme": "^0.1.1-alpha.3",
+ "@backstage/cli": "^0.1.1-alpha.4",
+ "@backstage/core": "^0.1.1-alpha.4",
+ "@backstage/theme": "^0.1.1-alpha.4",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
diff --git a/plugins/welcome/src/components/WelcomePage/WelcomePage.test.tsx b/plugins/welcome/src/components/WelcomePage/WelcomePage.test.tsx
index 9b78726f0a..9cba76fddc 100644
--- a/plugins/welcome/src/components/WelcomePage/WelcomePage.test.tsx
+++ b/plugins/welcome/src/components/WelcomePage/WelcomePage.test.tsx
@@ -18,7 +18,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/theme';
+import { lightTheme } from '@backstage/theme';
import { ApiProvider, ApiRegistry, errorApiRef } from '@backstage/core';
describe('WelcomePage', () => {
@@ -28,7 +28,7 @@ describe('WelcomePage', () => {
-
+
,