cli: add create-app
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* 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 path from 'path';
|
||||
import { promisify } from 'util';
|
||||
import chalk from 'chalk';
|
||||
import inquirer, { Answers, Question } from 'inquirer';
|
||||
import { exec as execCb } from 'child_process';
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import { realpathSync } from 'fs';
|
||||
import os from 'os';
|
||||
import { Task, templatingTask } from '../../helpers/tasks';
|
||||
const exec = promisify(execCb);
|
||||
|
||||
async function checkExists(rootDir: string, name: string) {
|
||||
await Task.forItem('checking', name, async () => {
|
||||
const destination = path.join(rootDir, 'plugins', name);
|
||||
|
||||
if (await fs.pathExists(destination)) {
|
||||
const existing = chalk.cyan(destination.replace(`${rootDir}/`, ''));
|
||||
throw new Error(
|
||||
`A directory with the same name already exists: ${existing}\nPlease try again with a different app name`,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function createTemporaryAppFolder(tempDir: string) {
|
||||
await Task.forItem('creating', 'temporary directory', async () => {
|
||||
try {
|
||||
await fs.mkdir(tempDir);
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to create temporary app directory: ${error.message}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function cleanUp(tempDir: string) {
|
||||
await Task.forItem('remove', 'temporary directory', async () => {
|
||||
await fs.remove(tempDir);
|
||||
});
|
||||
}
|
||||
|
||||
async function buildApp(pluginFolder: string) {
|
||||
const commands = ['yarn install', 'yarn build'];
|
||||
for (const command of commands) {
|
||||
await Task.forItem('executing', command, async () => {
|
||||
process.chdir(pluginFolder);
|
||||
|
||||
await exec(command).catch(error => {
|
||||
process.stdout.write(error.stderr);
|
||||
process.stdout.write(error.stdout);
|
||||
throw new Error(`Could not execute command ${chalk.cyan(command)}`);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function moveApp(
|
||||
tempDir: string,
|
||||
destination: string,
|
||||
id: string,
|
||||
) {
|
||||
await Task.forItem('moving', id, async () => {
|
||||
await fs.move(tempDir, destination).catch(error => {
|
||||
throw new Error(
|
||||
`Failed to move plugin from ${tempDir} to ${destination}: ${error.message}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default async () => {
|
||||
const questions: Question[] = [
|
||||
{
|
||||
type: 'input',
|
||||
name: 'name',
|
||||
message: chalk.blue('Enter a name for the app [required]'),
|
||||
validate: (value: any) => {
|
||||
if (!value) {
|
||||
return chalk.red('Please enter a name for the app');
|
||||
} else if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(value)) {
|
||||
return chalk.red(
|
||||
'Plugin name must be kebab-cased and contain only letters, digits, and dashes.',
|
||||
);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
];
|
||||
const answers: Answers = await inquirer.prompt(questions);
|
||||
|
||||
const rootDir = realpathSync(process.cwd());
|
||||
const cliPackage = resolvePath(__dirname, '../../..');
|
||||
const templateDir = resolvePath(cliPackage, 'templates', 'default-app');
|
||||
const tempDir = path.join(os.tmpdir(), answers.name);
|
||||
const appDir = path.join(rootDir, answers.name);
|
||||
const version = require(resolvePath(cliPackage, 'package.json')).version;
|
||||
|
||||
Task.log();
|
||||
Task.log('Creating the app...');
|
||||
|
||||
try {
|
||||
Task.section('Checking if the directory is available');
|
||||
await checkExists(rootDir, answers.name);
|
||||
|
||||
Task.section('Creating a temporary app directory');
|
||||
await createTemporaryAppFolder(tempDir);
|
||||
|
||||
Task.section('Preparing files');
|
||||
await templatingTask(templateDir, tempDir, { ...answers, version });
|
||||
|
||||
Task.section('Moving to final location');
|
||||
await moveApp(tempDir, appDir, answers.name);
|
||||
|
||||
Task.section('Building the app');
|
||||
await buildApp(appDir);
|
||||
|
||||
Task.log();
|
||||
Task.log(
|
||||
chalk.green(`🥇 Successfully created ${chalk.cyan(answers.name)}`),
|
||||
);
|
||||
Task.log();
|
||||
} catch (error) {
|
||||
Task.error(error.message);
|
||||
|
||||
Task.log('It seems that something went wrong when creating the app 🤔');
|
||||
Task.log('We are going to clean up, and then you can try again.');
|
||||
|
||||
Task.section('Cleanup');
|
||||
await cleanUp(tempDir);
|
||||
Task.error('🔥 Failed to create app!');
|
||||
}
|
||||
};
|
||||
@@ -17,6 +17,7 @@
|
||||
import program from 'commander';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs';
|
||||
import createAppCommand from './commands/create-app/createApp';
|
||||
import createPluginCommand from './commands/create-plugin/createPlugin';
|
||||
import watch from './commands/watch-deps';
|
||||
import lintCommand from './commands/lint';
|
||||
@@ -32,6 +33,11 @@ const main = (argv: string[]) => {
|
||||
|
||||
program.name('backstage-cli').version(packageJson.version ?? '0.0.0');
|
||||
|
||||
program
|
||||
.command('create-app')
|
||||
.description('Creates a new app in a new directory')
|
||||
.action(actionHandler(createAppCommand));
|
||||
|
||||
program
|
||||
.command('app:build')
|
||||
.description('Build an app for a production release')
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# [Backstage](https://backstage.io)
|
||||
|
||||
This is your newly scaffolded Backstage App, Good Luck!
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"packages": ["packages/*", "plugins/*"],
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"version": "0.1.0"
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "root",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "yarn build && yarn workspace app start",
|
||||
"build": "lerna run build",
|
||||
"test": "cross-env CI=true lerna run test -- --coverage",
|
||||
"create-plugin": "backstage-cli create-plugin",
|
||||
"lint": "lerna run lint"
|
||||
},
|
||||
"workspaces": {
|
||||
"packages": [
|
||||
"packages/*",
|
||||
"plugins/*"
|
||||
]
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^{{version}}",
|
||||
"cross-env": "^7.0.0",
|
||||
"lerna": "^3.20.2",
|
||||
"prettier": "^1.19.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "app",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@material-ui/core": "^4.9.1",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@backstage/cli": "^{{version}}",
|
||||
"@backstage/core": "^{{version}}",
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.3.2",
|
||||
"@testing-library/user-event": "^7.1.2",
|
||||
"@types/jest": "^24.0.0",
|
||||
"@types/node": "^12.0.0",
|
||||
"@types/react-router-dom": "^5.1.3",
|
||||
"plugin-welcome": "0.0.0",
|
||||
"react": "^16.12.0",
|
||||
"react-dom": "^16.12.0",
|
||||
"react-router-dom": "^5.1.2"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "backstage-cli app:serve",
|
||||
"build": "backstage-cli app:build",
|
||||
"test": "backstage-cli test",
|
||||
"lint": "backstage-cli lint"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 890 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,93 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Backstage is an open platform for building developer portals"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link
|
||||
rel="manifest"
|
||||
href="%PUBLIC_URL%/manifest.json"
|
||||
crossorigin="use-credentials"
|
||||
/>
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="%PUBLIC_URL%/apple-touch-icon.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="%PUBLIC_URL%/favicon-32x32.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="%PUBLIC_URL%/favicon-16x16.png"
|
||||
/>
|
||||
<link
|
||||
rel="mask-icon"
|
||||
href="%PUBLIC_URL%/safari-pinned-tab.svg"
|
||||
color="#5bbad5"
|
||||
/>
|
||||
<style>
|
||||
@media only screen {
|
||||
html {
|
||||
zoom: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 800px) {
|
||||
html {
|
||||
zoom: 0.85;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1200px) {
|
||||
html {
|
||||
zoom: 1;
|
||||
}
|
||||
}
|
||||
|
||||
#root {
|
||||
min-height: 100%;
|
||||
}
|
||||
</style>
|
||||
<title>Backstage</title>
|
||||
</head>
|
||||
<body style="margin: 0">
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"short_name": "Backstage",
|
||||
"name": "Backstage",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "48x48",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"start_url": "./index.html",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="512.000000pt" height="512.000000pt" viewBox="0 0 512.000000 512.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.11, written by Peter Selinger 2001-2013
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M492 4610 c-4 -3 -8 -882 -7 -1953 l0 -1948 850 2 c898 1 945 3 1118
|
||||
49 505 134 823 531 829 1037 2 136 -9 212 -44 323 -40 125 -89 218 -163 310
|
||||
-35 43 -126 128 -169 157 -22 15 -43 30 -46 33 -12 13 -131 70 -188 91 l-64
|
||||
22 60 28 c171 77 317 224 403 404 64 136 92 266 91 425 -5 424 -245 770 -642
|
||||
923 -79 30 -105 39 -155 50 -11 3 -38 10 -60 15 -22 6 -60 13 -85 17 -25 3
|
||||
-58 9 -75 12 -36 8 -1643 11 -1653 3z m1497 -743 c236 -68 352 -254 305 -486
|
||||
-26 -124 -110 -224 -232 -277 -92 -40 -151 -46 -439 -49 l-283 -3 -1 27 c-1
|
||||
36 -1 760 0 790 l1 23 298 -5 c226 -4 310 -9 351 -20z m-82 -1538 c98 -3 174
|
||||
-19 247 -52 169 -78 257 -212 258 -395 0 -116 -36 -221 -100 -293 -64 -72
|
||||
-192 -135 -314 -155 -23 -3 -181 -7 -350 -8 l-308 -2 -1 26 c-6 210 1 874 9
|
||||
879 9 5 366 6 559 0z"/>
|
||||
<path d="M4160 1789 c-275 -24 -499 -263 -503 -536 -1 -115 21 -212 66 -292
|
||||
210 -369 697 -402 950 -65 77 103 110 199 111 329 0 50 -6 113 -13 140 -16 58
|
||||
-62 155 -91 193 -33 43 -122 132 -132 132 -5 0 -26 11 -46 25 -85 56 -219 85
|
||||
-342 74z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import App from './App';
|
||||
|
||||
describe('App', () => {
|
||||
it('should render', () => {
|
||||
const rendered = render(<App />);
|
||||
expect(rendered.baseElement).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
import { CssBaseline, makeStyles, ThemeProvider } from '@material-ui/core';
|
||||
import { BackstageTheme, createApp } from '@backstage/core';
|
||||
import React, { FC } from 'react';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import * as plugins from './plugins';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
'@global': {
|
||||
html: {
|
||||
height: '100%',
|
||||
fontFamily: theme.typography.fontFamily,
|
||||
},
|
||||
body: {
|
||||
height: '100%',
|
||||
fontFamily: theme.typography.fontFamily,
|
||||
'overscroll-behavior-y': 'none',
|
||||
},
|
||||
a: {
|
||||
color: 'inherit',
|
||||
textDecoration: 'none',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const app = createApp();
|
||||
app.registerPlugin(...Object.values(plugins));
|
||||
const AppComponent = app.build();
|
||||
|
||||
const App: FC<{}> = () => {
|
||||
useStyles();
|
||||
return (
|
||||
<CssBaseline>
|
||||
<ThemeProvider theme={BackstageTheme}>
|
||||
<Router>
|
||||
<AppComponent />
|
||||
</Router>
|
||||
</ThemeProvider>
|
||||
</CssBaseline>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,5 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root'));
|
||||
@@ -0,0 +1 @@
|
||||
export { default as WelcomePlugin } from 'plugin-welcome';
|
||||
@@ -0,0 +1 @@
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "@spotify/web-scripts/config/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "react",
|
||||
"incremental": false
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Title
|
||||
|
||||
Welcome to the welcome plugin!
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "plugin-welcome",
|
||||
"version": "0.0.0",
|
||||
"main": "dist/cjs/index.js",
|
||||
"types": "dist/cjs/index.d.ts",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "backstage-cli plugin:build",
|
||||
"lint": "backstage-cli lint",
|
||||
"test": "backstage-cli test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/core": "^{{version}}",
|
||||
"@backstage/cli": "^{{version}}",
|
||||
"@types/testing-library__jest-dom": "5.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@material-ui/lab": "4.0.0-alpha.45"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import React, { FC } from 'react';
|
||||
import { HeaderLabel } from '@backstage/core';
|
||||
|
||||
const timeFormat = { hour: '2-digit', minute: '2-digit' };
|
||||
const utcOptions = { timeZone: 'UTC', ...timeFormat };
|
||||
const nycOptions = { timeZone: 'America/New_York', ...timeFormat };
|
||||
const tyoOptions = { timeZone: 'Asia/Tokyo', ...timeFormat };
|
||||
const stoOptions = { timeZone: 'Europe/Stockholm', ...timeFormat };
|
||||
|
||||
const defaultTimes = {
|
||||
timeNY: '',
|
||||
timeUTC: '',
|
||||
timeTYO: '',
|
||||
timeSTO: '',
|
||||
};
|
||||
|
||||
function getTimes() {
|
||||
const d = new Date();
|
||||
const lang = window.navigator.language;
|
||||
|
||||
// Using the browser native toLocaleTimeString instead of huge moment-tz
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleTimeString
|
||||
const timeNY = d.toLocaleTimeString(lang, nycOptions);
|
||||
const timeUTC = d.toLocaleTimeString(lang, utcOptions);
|
||||
const timeTYO = d.toLocaleTimeString(lang, tyoOptions);
|
||||
const timeSTO = d.toLocaleTimeString(lang, stoOptions);
|
||||
|
||||
return { timeNY, timeUTC, timeTYO, timeSTO };
|
||||
}
|
||||
|
||||
const HomePageTimer: FC<{}> = () => {
|
||||
const [{ timeNY, timeUTC, timeTYO, timeSTO }, setTimes] = React.useState(
|
||||
defaultTimes,
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
setTimes(getTimes());
|
||||
|
||||
const intervalId = setInterval(() => {
|
||||
setTimes(getTimes());
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
clearInterval(intervalId);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<HeaderLabel label="NYC" value={timeNY} />
|
||||
<HeaderLabel label="UTC" value={timeUTC} />
|
||||
<HeaderLabel label="STO" value={timeSTO} />
|
||||
<HeaderLabel label="TYO" value={timeTYO} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default HomePageTimer;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './Timer';
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import WelcomePage from './WelcomePage';
|
||||
import { ThemeProvider } from '@material-ui/core';
|
||||
import { BackstageTheme } from '@backstage/core';
|
||||
|
||||
describe('WelcomePage', () => {
|
||||
it('should render', () => {
|
||||
const rendered = render(
|
||||
<ThemeProvider theme={BackstageTheme}>
|
||||
<WelcomePage />
|
||||
</ThemeProvider>,
|
||||
);
|
||||
expect(rendered.baseElement).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
import React, { FC } from 'react';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import {
|
||||
Typography,
|
||||
Grid,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
Link,
|
||||
} from '@material-ui/core';
|
||||
import Timer from '../Timer';
|
||||
import {
|
||||
Content,
|
||||
InfoCard,
|
||||
Header,
|
||||
Page,
|
||||
pageTheme,
|
||||
ContentHeader,
|
||||
SupportButton,
|
||||
} from '@backstage/core';
|
||||
|
||||
const WelcomePage: FC<{}> = () => {
|
||||
const profile = { givenName: '' };
|
||||
|
||||
return (
|
||||
<Page theme={pageTheme.home}>
|
||||
<Header
|
||||
title={`Welcome ${profile.givenName || 'to Backstage'}`}
|
||||
subtitle="Some quick intro and links."
|
||||
>
|
||||
<Timer />
|
||||
</Header>
|
||||
<Content>
|
||||
<ContentHeader title="Getting Started">
|
||||
<SupportButton />
|
||||
</ContentHeader>
|
||||
<Grid container>
|
||||
<Grid item xs={12} md={6}>
|
||||
<InfoCard maxWidth>
|
||||
<Typography variant="body1" gutterBottom>
|
||||
You now have a running instance of Backstage!
|
||||
<span role="img" aria-label="confetti">
|
||||
🎉
|
||||
</span>
|
||||
Let's make sure you get the most out of this platform by walking
|
||||
you through the basics.
|
||||
</Typography>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
The Setup
|
||||
</Typography>
|
||||
<Typography variant="body1" paragraph>
|
||||
Backstage is put together from three base concepts: the core,
|
||||
the app and the plugins.
|
||||
</Typography>
|
||||
<List>
|
||||
<ListItem>
|
||||
<ListItemText primary="The core is responsible for base functionality." />
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemText primary="The app provides the base UI and connects the plugins." />
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary="The plugins make Backstage useful for the end users with
|
||||
specific views and functionality."
|
||||
/>
|
||||
</ListItem>
|
||||
</List>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Try It Out
|
||||
</Typography>
|
||||
<Typography variant="body1" paragraph>
|
||||
We suggest you either check out the documentation for{' '}
|
||||
<Link href="https://github.com/spotify/backstage/blob/master/docs/getting-started/create-a-plugin.md">
|
||||
creating a plugin
|
||||
</Link>{' '}
|
||||
or have a look in the code for the{' '}
|
||||
<Link component={RouterLink} to="/home">
|
||||
Home Page
|
||||
</Link>{' '}
|
||||
in the directory "plugins/home-page/src".
|
||||
</Typography>
|
||||
</InfoCard>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<InfoCard>
|
||||
<Typography variant="h5">Quick Links</Typography>
|
||||
<List>
|
||||
<ListItem>
|
||||
<Link href="https://backstage.io">backstage.io</Link>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<Link href="https://github.com/spotify/backstage/blob/master/docs/getting-started/create-a-plugin.md">
|
||||
Create a plugin
|
||||
</Link>
|
||||
</ListItem>
|
||||
</List>
|
||||
</InfoCard>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Content>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default WelcomePage;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './WelcomePage';
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './plugin';
|
||||
@@ -0,0 +1,7 @@
|
||||
import plugin from './plugin';
|
||||
|
||||
describe('welcome', () => {
|
||||
it('should export plugin', () => {
|
||||
expect(plugin).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
import { createPlugin } from '@backstage/core';
|
||||
import WelcomePage from './components/WelcomePage';
|
||||
|
||||
export default createPlugin({
|
||||
id: 'welcome',
|
||||
register({ router }) {
|
||||
router.registerRoute('/', WelcomePage);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('@spotify/web-scripts/config/prettier.config.js');
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "@spotify/web-scripts/config/tsconfig.json",
|
||||
"exclude": ["**/*.test.*"],
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"noEmit": false,
|
||||
"declarationMap": true,
|
||||
"incremental": true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user