feat(scaffolder): adding some more stuff and some initial sample template

This commit is contained in:
blam
2020-04-28 22:38:34 +02:00
parent e104fb007c
commit 9635983e5a
53 changed files with 2679 additions and 1600 deletions
+1
View File
@@ -23,6 +23,7 @@
"@backstage/plugin-home-page": "^0.1.1-alpha.4",
"@backstage/plugin-inventory": "^0.1.1-alpha.4",
"@backstage/plugin-lighthouse": "^0.1.1-alpha.4",
"@backstage/plugin-scaffolder": "^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",
+1
View File
@@ -18,3 +18,4 @@ export { plugin as HomePagePlugin } from '@backstage/plugin-home-page';
export { plugin as WelcomePlugin } from '@backstage/plugin-welcome';
export { plugin as LighthousePlugin } from '@backstage/plugin-lighthouse';
export { plugin as InventoryPlugin } from '@backstage/plugin-inventory';
export { plugin as ScaffolderPlugin } from '@backstage/plugin-scaffolder';
+4 -2
View File
@@ -28,20 +28,22 @@ import helmet from 'helmet';
import compression from 'compression';
import { testRouter } from './test';
import { router as inventoryRouter } from '@backstage/plugin-inventory-backend';
import { router as scaffolderRouter } from '@backstage/plugin-scaffolder-backend';
import { createScaffolder } from '@backstage/plugin-scaffolder-backend';
const DEFAULT_PORT = 7000;
const PORT = parseInt(process.env.PORT ?? '', 10) || DEFAULT_PORT;
const app = express();
const scaffolder = createScaffolder();
app.use(helmet());
app.use(cors());
app.use(compression());
app.use(express.json());
app.use('/test', testRouter);
app.use('/inventory', inventoryRouter);
app.use('/scaffolder', scaffolderRouter);
app.use('/scaffolder', scaffolder);
app.listen(PORT, () => {
console.log(`Listening on port ${PORT}`);
+1
View File
@@ -1,4 +1,5 @@
module.exports = {
extends: [require.resolve('@backstage/cli/config/eslint')],
rules: {
'no-console': 0, // Permitted in console programs
'new-cap': ['error', { capIsNew: false }], // Because Express constructs things e.g. like 'const r = express.Router()'
@@ -0,0 +1,12 @@
#!/bin/bash
# package name is "__component_id__" so that yarn doesn't throw an error
# about invalid characters when running yarn commands. here we replace it with the actual name
sed -i -e "s/__component_id__/{{ cookiecutter.component_id }}/g" package.json
# node_modules was moved out of the template folder, during the pre_gen hook,
# to avoid cookie_cutter from copying all of them. time to move it back
mv ../../node_modules.tmp ../../\{\{cookiecutter.component_id\}\}/node_modules 2>/dev/null ||:
# move back the build directory that was moved out in the pre_gen hook (if it exists)
mv ../../build.tmp ../../\{\{cookiecutter.component_id\}\}/build 2>/dev/null ||:
@@ -0,0 +1,9 @@
#!/bin/bash
# no way to ignore files in cookiecutter, so move node_modules out while building
# to avoid cookiecutter from copying all of them
mv ../../\{\{cookiecutter.component_id\}\}/node_modules ../../node_modules.tmp 2>/dev/null ||:
# cookicutter really doesn't like the next.js build directory, so if the app has
# been built from inside the template folder, that folders needs to be moved out as well
mv ../../\{\{cookiecutter.component_id\}\}/build ../../build.tmp 2>/dev/null ||:
@@ -0,0 +1,6 @@
{
"id": "react-ssr-template",
"name": "SSR React Website",
"description": "Next.js application skeleton for creating isomorphic web applications.",
"ownerId": "something"
}
@@ -0,0 +1,13 @@
# editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
insert_final_newline = false
@@ -0,0 +1 @@
module.exports = require('@spotify/web-scripts/config/eslintrc.js');
@@ -0,0 +1,39 @@
name: Frontend CI
on:
push:
paths:
- '.'
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.x]
steps:
- name: checkout code
uses: actions/checkout@v1
- name: get yarn cache
id: yarn-cache
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v1
with:
path: ${{ steps.yarn-cache.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: use node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: yarn install, build, and test
working-directory: .
run: |
yarn install
yarn build --if-present
yarn test
env:
CI: true
@@ -0,0 +1,18 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
/node_modules
# testing
/coverage
junit.xml
# build
/build
# misc
.DS_Store
npm-debug.log*
yarn-debug.log*
yarn-error.log*
@@ -0,0 +1,14 @@
# {{ cookiecutter.component_id }}
## Description
{{ cookiecutter.description }}
## Develop
```bash
# install dependencies
$ yarn
# start development server
$ yarn dev
```
@@ -0,0 +1,4 @@
module.exports = {
plugins: ['babel-plugin-styled-components'],
presets: ['next/babel', '@zeit/next-typescript/babel'],
};
@@ -0,0 +1,8 @@
module.exports = {
...require('@spotify/web-scripts/config/jest.config.js'),
testEnvironment: 'jsdom',
testPathIgnorePatterns: ['/node_modules/', '/build/'],
transform: {
'^.+\\.tsx?$': 'babel-jest',
},
};
@@ -0,0 +1,5 @@
// read more about this file here ---> https://github.com/zeit/next.js/blob/canary/docs/basic-features/typescript.md
/* eslint spaced-comment: ["error", "always", { "markers": ["/"] }] */
/// <reference types="next" />
/// <reference types="next/types/global" />
@@ -0,0 +1,3 @@
module.exports = {
distDir: 'build',
};
@@ -0,0 +1,52 @@
{
"name": "__component_id__",
"version": "0.0.0",
"description": "{{ cookiecutter.description }}",
"license": "UNLICENSED",
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start",
"lint": "web-scripts lint --ignore-path=.gitignore",
"test": "web-scripts test --config jest.config.js",
"pretest:ci": "yarn lint",
"test:ci": "yarn test --ci --coverage --reporters=default --reporters=jest-junit"
},
"dependencies": {
"@zeit/next-typescript": "^1.1.1",
"babel-plugin-styled-components": "^1.10.6",
"next": "^9.1.1",
"react": "^16.8.5",
"react-dom": "^16.8.5",
"styled-components": "^4.3.2"
},
"devDependencies": {
"@spotify/tsconfig": "^5.0.0",
"@spotify/web-scripts": "^5.0.0",
"@testing-library/react": "^8.0.1",
"@types/node": "^13.1.4",
"@types/react": "^16.8.7",
"@types/react-dom": "^16.8.2",
"@types/styled-components": "^4.1.18",
"husky": "^2.7.0",
"jest-junit": "^8.0.0",
"typescript": "^3.4.5"
},
"husky": {
"hooks": {
"pre-commit": "web-scripts precommit"
}
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
@@ -0,0 +1 @@
module.exports = require('@spotify/web-scripts/config/prettier.config.js');
@@ -0,0 +1,13 @@
import React from 'react';
import { cleanup, render } from '@testing-library/react';
import Index from '../pages/index';
afterEach(cleanup);
describe('Index', () => {
it('Says hello', () => {
const { queryByText } = render(<Index />);
expect(queryByText('Hello!')).toBeTruthy();
});
});
@@ -0,0 +1,3 @@
import React from 'react';
export const Header = () => <h1>Header</h1>;
@@ -0,0 +1,37 @@
import React from 'react';
import App from 'next/app';
import Head from 'next/head';
import styled from 'styled-components';
import { Header } from '../components/Header';
const StyledApp = styled.div`
> * {
padding-left: 16px;
padding-right: 16px;
}
`;
const Main = styled.div`
margin: 2em auto;
height: 85vh;
`;
class CustomApp extends App {
render() {
const { Component, pageProps } = this.props;
return (
<StyledApp>
<Head>
<link rel="stylesheet" href="/static/fonts.css" />
</Head>
<Header />
<Main>
<Component {...pageProps} />
</Main>
</StyledApp>
);
}
}
export default CustomApp;
@@ -0,0 +1,41 @@
/**
* This file extends the <Document /> and injects the server side rendered styles into the <head>
* By server-side rendering CSS we avoid visual changes in the layout while loading the JS.
*
* Taken from this example:
* https://github.com/zeit/next.js/tree/master/examples/with-styled-components
*/
import React from 'react';
import Document, { DocumentContext, DocumentInitialProps } from 'next/document';
import { ServerStyleSheet } from 'styled-components';
export default class MyDocument extends Document {
static async getInitialProps(
ctx: DocumentContext,
): Promise<DocumentInitialProps> {
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App: any) => props =>
sheet.collectStyles(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
};
} finally {
sheet.seal();
}
}
}
@@ -0,0 +1,5 @@
import { NextApiRequest, NextApiResponse } from 'next';
export default function handle(_: NextApiRequest, res: NextApiResponse) {
res.status(200).send('ok');
}
@@ -0,0 +1,5 @@
import React from 'react';
const Index = () => <h1>Hello!</h1>;
export default Index;
@@ -0,0 +1,20 @@
{
"extends": "@spotify/tsconfig",
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve"
},
"exclude": ["node_modules", "output/node_modules"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
}
+18 -2
View File
@@ -14,5 +14,21 @@
* limitations under the License.
*/
export { router } from './plugin';
export const createScaffolder = () => {};
import { TemplaterConfig, createTemplater } from './lib/templater';
import { createStorage, StorageConfig } from './lib/storage';
import express from 'express';
export const createScaffolder = (config?: TemplaterConfig & StorageConfig) => {
const store = createStorage(config);
const templater = createTemplater(config);
const router = express.Router();
router.get('/v1/templates', async (_, res) => {
const templates = await store.list();
res.status(200).json(templates);
});
return router;
};
@@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { DiskRepository } from './disk';
import { DiskStorage } from './disk';
import * as path from 'path';
describe('Template Repository', () => {
describe('Disk Storage', () => {
it('should load a simple template from a simple directory', async () => {
const testTemplateDir = path.resolve(
__dirname,
@@ -23,7 +23,7 @@ describe('Template Repository', () => {
);
const templateInfo = require(`${testTemplateDir}/mock-template/template-info.json`);
const repository = new DiskRepository(testTemplateDir);
const repository = new DiskStorage(testTemplateDir);
await repository.reindex();
@@ -42,7 +42,7 @@ describe('Template Repository', () => {
'../../../test/mock-multiple-templates-dir',
);
const repository = new DiskRepository(testTemplateDir);
const repository = new DiskStorage(testTemplateDir);
await repository.reindex();
@@ -57,7 +57,7 @@ describe('Template Repository', () => {
'/some-folder-that-deffo-does-not-exist',
);
const repository = new DiskRepository(testTemplateDir);
const repository = new DiskStorage(testTemplateDir);
await repository.reindex();
@@ -72,7 +72,7 @@ describe('Template Repository', () => {
'../../../test/mock-failing-template-dir',
);
const repository = new DiskRepository(testTemplateDir);
const repository = new DiskStorage(testTemplateDir);
await repository.reindex();
@@ -16,19 +16,21 @@
import glob from 'glob';
import fs from 'fs-extra';
import { logger } from 'lib/logger';
import { Template, RepositoryBase as Base } from '.';
import { logger } from '../logger';
import { Template, StorageBase as Base } from '.';
interface DiskIndexEntry {
contents: Template;
location: string;
}
export class DiskRepository implements Base {
export class DiskStorage implements Base {
private repository: Template[] = [];
private localIndex: DiskIndexEntry[] = [];
constructor(private repoDir = `${__dirname}/templates`) {}
constructor(private repoDir = `${__dirname}/../../../sample-templates`) {
console.warn(require('path').resolve(repoDir));
}
public async list(): Promise<Template[]> {
if (this.repository.length === 0) {
await this.reindex();
@@ -97,5 +99,3 @@ export class DiskRepository implements Base {
});
}
}
export const Repository = new DiskRepository();
@@ -13,10 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { RepositoryBase, Repository } from '.';
import { StorageBase, createStorage } from '.';
describe('Repository Interface Test', () => {
const mockRepo = new (class MockRepository implements RepositoryBase {
describe('Storage Interface Test', () => {
const mockStore = new (class MockStorage implements StorageBase {
list = jest.fn();
prepare = jest.fn();
reindex = jest.fn();
@@ -28,29 +28,28 @@ describe('Repository Interface Test', () => {
};
})();
afterEach(() => mockRepo.reset());
afterEach(() => mockStore.reset());
it('should call list of the set repo when calling list', async () => {
Repository.setRepository(mockRepo);
const store = createStorage({ store: mockStore });
await store.list();
await Repository.list();
expect(mockRepo.list).toHaveBeenCalled();
expect(mockStore.list).toHaveBeenCalled();
});
it('should reindex on the repo when calling reindex', async () => {
Repository.setRepository(mockRepo);
const store = createStorage({ store: mockStore });
await Repository.reindex();
await store.reindex();
expect(mockRepo.reindex).toHaveBeenCalled();
expect(mockStore.reindex).toHaveBeenCalled();
});
it('should call prepare with the correct id when calling prepare', async () => {
Repository.setRepository(mockRepo);
const store = createStorage({ store: mockStore });
await Repository.prepare('testid');
await store.prepare('testid');
expect(mockRepo.prepare).toHaveBeenCalledWith('testid');
expect(mockStore.prepare).toHaveBeenCalledWith('testid');
});
});
@@ -1,4 +1,4 @@
import { Repository as DiskRepository } from './disk';
import { DiskStorage } from './disk';
/*
* Copyright 2020 Spotify AB
@@ -22,7 +22,7 @@ export interface Template {
ownerId: string;
}
export abstract class RepositoryBase {
export abstract class StorageBase {
// lists all templates available
abstract async list(): Promise<Template[]>;
// can be used to build an index of the available templates;
@@ -31,20 +31,24 @@ export abstract class RepositoryBase {
abstract async prepare(id: string): Promise<string>;
}
class RepositoryImplementation implements RepositoryBase {
repo?: RepositoryBase;
constructor() {
this.repo = DiskRepository;
}
public setRepository(repo: RepositoryBase) {
this.repo = repo;
}
list = () => this.repo!.list();
prepare = (id: string) => this.repo!.prepare(id);
reindex = () => this.repo!.reindex();
export interface StorageConfig {
store?: StorageBase;
}
export const Repository = new RepositoryImplementation();
class Storage implements StorageBase {
store?: StorageBase;
constructor({ store }: StorageConfig) {
this.store = store;
}
list = () => this.store!.list();
prepare = (id: string) => this.store!.prepare(id);
reindex = () => this.store!.reindex();
}
export const createStorage = (
config: StorageConfig = { store: new DiskStorage() },
): StorageBase => {
return new Storage(config);
};
@@ -17,7 +17,7 @@ import { TemplaterBase, TemplaterRunOptions } from '.';
*/
import fs from 'fs-extra';
class CookieCutter implements TemplaterBase {
export class CookieCutter implements TemplaterBase {
public async run(options: TemplaterRunOptions): Promise<string> {
// first we need to make cookiecutter.json in the directory provided with the input values.
const cookieInfo = {
@@ -30,5 +30,3 @@ class CookieCutter implements TemplaterBase {
// run cookie cutter with new json
}
}
export const CookieCutterTemplater = new CookieCutter();
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { CookieCutterTemplater } from './cookiecutter';
import { CookieCutter } from './cookiecutter';
export interface RequiredTemplateValues {
componentId: string;
@@ -29,14 +29,14 @@ export abstract class TemplaterBase {
abstract async run(opts: TemplaterRunOptions): Promise<string>;
}
class TemplaterImplementation implements TemplaterBase {
export interface TemplaterConfig {
templater?: TemplaterBase;
}
class Templater implements TemplaterBase {
templater?: TemplaterBase;
constructor() {
this.templater = CookieCutterTemplater;
}
public setTemplater(templater: TemplaterBase) {
constructor({ templater }: TemplaterConfig) {
this.templater = templater;
}
@@ -45,4 +45,8 @@ class TemplaterImplementation implements TemplaterBase {
}
}
export const Templater = new TemplaterImplementation();
export const createTemplater = (
config: TemplaterConfig = { templater: new CookieCutter() },
): TemplaterBase => {
return new Templater(config);
};
@@ -1,43 +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 { router } from './plugin';
import { Repository, Template } from 'lib/repo';
import supertest from 'supertest';
import express from 'express';
describe('scaffolder backend', () => {
const app = express().use(router);
const mockTemplate: Template = {
id: 'test-mock-template',
name: 'mock',
description: 'test for tests',
ownerId: 'lol',
};
it('should return a list of templates that are in the directory which the plugin is initialised in', async () => {
jest.spyOn(Repository, 'list').mockResolvedValue([mockTemplate]);
const { body } = await supertest(app)
.get('/v1/templates')
.send();
expect(body.length).toBe(1);
expect(body[0]).toStrictEqual(mockTemplate);
});
});
-82
View File
@@ -1,82 +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.
*/
/*
* 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 express from 'express';
export const router = express.Router();
router.get('/v1/templates', async (_, res) => {
const templates = await Repository.list();
res.status(200).json(templates);
});
router.get('/v1/template/:templateId', async (_, res) => {
res
.status(200)
.send([
{ id: 'component1' },
{ id: 'component2' },
{ id: 'component3' },
{ id: 'component4' },
]);
});
router.post('/v1/jobs', async (_, res) => {
res
.status(200)
.send([
{ id: 'component1' },
{ id: 'component2' },
{ id: 'component3' },
{ id: 'component4' },
]);
});
router.get('/v1/jobs', async (_, res) => {
res
.status(200)
.send([
{ id: 'component1' },
{ id: 'component2' },
{ id: 'component3' },
{ id: 'component4' },
]);
});
router.get('/v1/job/:jobId', async (_, res) => {
res
.status(200)
.send([
{ id: 'component1' },
{ id: 'component2' },
{ id: 'component3' },
{ id: 'component4' },
]);
});
@@ -0,0 +1,6 @@
{
"id": "mock-template-2",
"name": "mockmannen",
"description": "mock template for building stuff in backstage",
"ownerId": "blam"
}
@@ -0,0 +1,7 @@
{
"id": "mock-template",
"name": "mockmannen",
"description": "mock template for building stuff in backstage",
"ownerId": "blam",
<heres some invalid hson>
}
@@ -0,0 +1,6 @@
{
"id": "mock-template-2",
"name": "mockmannen",
"description": "mock template for building stuff in backstage",
"ownerId": "blam"
}
@@ -0,0 +1,6 @@
{
"id": "mock-template",
"name": "mockmannen",
"description": "mock template for building stuff in backstage",
"ownerId": "blam"
}
@@ -0,0 +1,6 @@
{
"id": "mock-template",
"name": "mockmannen",
"description": "mock template for building stuff in backstage",
"ownerId": "blam"
}
+7 -10
View File
@@ -1,14 +1,11 @@
{
"include": ["src"],
"extends": "../../packages/backend/tsconfig.json",
"include": [
"./src"
],
"compilerOptions": {
"baseUrl": "src",
"outDir": "dist",
"incremental": true,
"sourceMap": true,
"declaration": true,
"strict": true,
"target": "es5",
"module": "commonjs",
"esModuleInterop": true
"baseUrl": "./src",
"outDir": "./dist",
"skipLibCheck": true
}
}
+1 -4
View File
@@ -1,6 +1,3 @@
module.exports = {
rules: {
'no-console': 0, // Permitted in console programs
'new-cap': ['error', { capIsNew: false }], // Because Express constructs things e.g. like 'const r = express.Router()'
},
extends: [require.resolve('@backstage/cli/config/eslint')],
};
+13
View File
@@ -0,0 +1,13 @@
# Inventory Frontend
WORK IN PROGRESS
This is the frontend part of the default inventory plugin.
It will implement the core API for handling your inventory of software, and
supply the base views to show and manage them.
## Links
- (Backend part of the plugin)[https://github.com/spotify/backstage/tree/master/plugins/inventory-backend]
- (The Backstage homepage)[https://backstage.io]
+38
View File
@@ -0,0 +1,38 @@
{
"name": "@backstage/plugin-scaffolder",
"version": "0.1.1-alpha.4",
"main": "dist/index.cjs.js",
"types": "dist/index.d.ts",
"license": "Apache-2.0",
"private": true,
"scripts": {
"build": "backstage-cli plugin:build",
"start": "backstage-cli plugin:serve",
"lint": "backstage-cli lint",
"test": "backstage-cli test",
"clean": "backstage-cli clean"
},
"devDependencies": {
"@backstage/cli": "^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/jest": "^24.0.0",
"@types/node": "^12.0.0",
"@types/testing-library__jest-dom": "5.0.2",
"jest-fetch-mock": "^3.0.3"
},
"dependencies": {
"@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",
"@material-ui/lab": "4.0.0-alpha.45",
"react": "16.13.1",
"react-dom": "16.13.1",
"react-use": "^13.0.0"
},
"files": [
"dist"
]
}
@@ -0,0 +1,52 @@
/*
* 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 { Content, InfoCard, Header, Page, pageTheme } from '@backstage/core';
// TODO(blam): Connect to backend
const STATIC_DATA = [
{
id: 'react-ssr-template',
name: 'SSR React Website',
description:
'Next.js application skeleton for creating isomorphic web applications.',
ownerId: 'something',
},
];
const ScaffolderPage: React.FC<{}> = () => {
return (
<Page theme={pageTheme.home}>
<Header title="Create a new component" subtitle="All your stuff" />
<Content>
<div style={{ display: 'flex' }}>
{STATIC_DATA.map(item => {
return (
<InfoCard
title={item.name}
deepLink={{ title: 'Create', link: '#' }}
>
<p>{item.description}</p>
</InfoCard>
);
})}
</div>
</Content>
</Page>
);
};
export default ScaffolderPage;
+17
View File
@@ -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 { plugin } from './plugin';
+23
View File
@@ -0,0 +1,23 @@
/*
* 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 { plugin } from './plugin';
describe('inventory', () => {
it('should export plugin', () => {
expect(plugin).toBeDefined();
});
});
+25
View File
@@ -0,0 +1,25 @@
/*
* 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 { createPlugin } from '@backstage/core';
import ScaffolderPage from './components/ScaffolderPage';
export const plugin = createPlugin({
id: 'inventory',
register({ router }) {
router.registerRoute('/scaffolder', ScaffolderPage);
},
});
+18
View File
@@ -0,0 +1,18 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import '@testing-library/jest-dom/extend-expect';
require('jest-fetch-mock').enableMocks();
+7
View File
@@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.json",
"include": ["src"],
"compilerOptions": {
"baseUrl": "src"
}
}
+2052 -1403
View File
File diff suppressed because it is too large Load Diff