feat(scaffolder): adding some more stuff and some initial sample template
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,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()'
|
||||
|
||||
+12
@@ -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 ||:
|
||||
+9
@@ -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"
|
||||
}
|
||||
+13
@@ -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
|
||||
+1
@@ -0,0 +1 @@
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
module.exports = require('@spotify/web-scripts/config/eslintrc.js');
|
||||
+39
@@ -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
|
||||
+18
@@ -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*
|
||||
+1
@@ -0,0 +1 @@
|
||||
lts/*
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
# {{ cookiecutter.component_id }}
|
||||
|
||||
## Description
|
||||
|
||||
{{ cookiecutter.description }}
|
||||
|
||||
## Develop
|
||||
|
||||
```bash
|
||||
# install dependencies
|
||||
$ yarn
|
||||
# start development server
|
||||
$ yarn dev
|
||||
```
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
plugins: ['babel-plugin-styled-components'],
|
||||
presets: ['next/babel', '@zeit/next-typescript/babel'],
|
||||
};
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
...require('@spotify/web-scripts/config/jest.config.js'),
|
||||
testEnvironment: 'jsdom',
|
||||
testPathIgnorePatterns: ['/node_modules/', '/build/'],
|
||||
transform: {
|
||||
'^.+\\.tsx?$': 'babel-jest',
|
||||
},
|
||||
};
|
||||
+5
@@ -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" />
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
distDir: 'build',
|
||||
};
|
||||
+52
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
module.exports = require('@spotify/web-scripts/config/prettier.config.js');
|
||||
+13
@@ -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();
|
||||
});
|
||||
});
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import React from 'react';
|
||||
|
||||
export const Header = () => <h1>Header</h1>;
|
||||
+37
@@ -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;
|
||||
+41
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import { NextApiRequest, NextApiResponse } from 'next';
|
||||
|
||||
export default function handle(_: NextApiRequest, res: NextApiResponse) {
|
||||
res.status(200).send('ok');
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
const Index = () => <h1>Hello!</h1>;
|
||||
|
||||
export default Index;
|
||||
+20
@@ -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"]
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
+6
-6
@@ -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();
|
||||
|
||||
+6
-6
@@ -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
-14
@@ -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');
|
||||
});
|
||||
});
|
||||
+21
-17
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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' },
|
||||
]);
|
||||
});
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"id": "mock-template-2",
|
||||
"name": "mockmannen",
|
||||
"description": "mock template for building stuff in backstage",
|
||||
"ownerId": "blam"
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"id": "mock-template",
|
||||
"name": "mockmannen",
|
||||
"description": "mock template for building stuff in backstage",
|
||||
"ownerId": "blam",
|
||||
<heres some invalid hson>
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"id": "mock-template-2",
|
||||
"name": "mockmannen",
|
||||
"description": "mock template for building stuff in backstage",
|
||||
"ownerId": "blam"
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"id": "mock-template",
|
||||
"name": "mockmannen",
|
||||
"description": "mock template for building stuff in backstage",
|
||||
"ownerId": "blam"
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"id": "mock-template",
|
||||
"name": "mockmannen",
|
||||
"description": "mock template for building stuff in backstage",
|
||||
"ownerId": "blam"
|
||||
}
|
||||
@@ -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,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')],
|
||||
};
|
||||
|
||||
@@ -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]
|
||||
@@ -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;
|
||||
@@ -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';
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
},
|
||||
});
|
||||
@@ -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();
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": ["src"],
|
||||
"compilerOptions": {
|
||||
"baseUrl": "src"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user