chore(scaffolder): added simple create react app scaffolder

This commit is contained in:
blam
2020-07-16 06:23:42 +02:00
parent c4986e7922
commit adce138843
9 changed files with 138 additions and 4 deletions
@@ -21,6 +21,7 @@ import {
GithubPreparer,
Preparers,
GithubPublisher,
CreateReactAppTemplater,
Templaters,
} from '@backstage/plugin-scaffolder-backend';
import { Octokit } from '@octokit/rest';
@@ -29,8 +30,10 @@ import Docker from 'dockerode';
export default async function createPlugin({ logger }: PluginEnvironment) {
const cookiecutterTemplater = new CookieCutter();
const craTemplater = new CreateReactAppTemplater();
const templaters = new Templaters();
templaters.register('cookiecutter', cookiecutterTemplater);
templaters.register('cra', craTemplater);
const filePreparer = new FilePreparer();
const githubPreparer = new GithubPreparer();
+2 -1
View File
@@ -39,7 +39,8 @@
"morgan": "^1.10.0",
"nodegit": "0.26.5",
"uuid": "^8.2.0",
"winston": "^3.2.1"
"winston": "^3.2.1",
"yaml": "^1.10.0"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.14",
@@ -0,0 +1,33 @@
apiVersion: backstage.io/v1alpha1
kind: Template
metadata:
name: create-react-app-template
title: Create React App Template
description: A template that scaffolds an applications using the Create React App (CRA) templater.
tags:
- Experimental
- React
- CRA
spec:
owner: web@example.com
templater: cra
type: website
path: '.'
schema:
required:
- component_id
- use_typescript
properties:
component_id:
title: Name
type: string
description: Unique name of the component
description:
title: Description
type: string
description: Description of the component
use_typescript:
title: Use Typescript
type: boolean
description: Include typescript
default: true
@@ -3,6 +3,7 @@
for URL in \
'react-ssr-template' \
'springboot-template' \
'create-react-app' \
; do \
curl \
--location \
@@ -23,7 +23,10 @@ export const makeLogStream = (meta: Record<string, JsonValue>) => {
// Create an empty stream to collect all the log lines into
// one variable for the API.
const stream = new PassThrough();
stream.on('data', chunk => log.push(chunk.toString()));
stream.on('data', chunk => {
const textValue = chunk.toString().trim();
if (textValue?.length > 1) log.push(textValue);
});
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
@@ -0,0 +1,80 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import fs from 'fs-extra';
import { runDockerContainer } from './helpers';
import { TemplaterBase, TemplaterRunOptions } from '.';
import path from 'path';
import { TemplaterRunResult } from './types';
import * as yaml from 'yaml';
export class CreateReactAppTemplater implements TemplaterBase {
public async run(options: TemplaterRunOptions): Promise<TemplaterRunResult> {
const {
component_id: componentName,
use_typescript: withTypescript,
description,
owner,
} = options.values;
const resultDir = await fs.promises.mkdtemp(`${options.directory}-result`);
await runDockerContainer({
imageName: 'node:lts-alpine',
args: [
'create-react-app',
componentName as string,
withTypescript ? ' --template typescript' : '',
],
templateDir: options.directory,
resultDir,
logStream: options.logStream,
dockerClient: options.dockerClient,
createOptions: {
Entrypoint: ['npx'],
WorkingDir: '/result',
},
});
// Need to also make a component-info.yaml to store the data about the service.
const componentInfo = {
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
metadata: {
name: componentName,
description,
},
spec: {
type: 'website',
lifecycle: 'experimental',
owner,
},
};
const finalDir = path.resolve(
resultDir,
options.values.component_id as string,
);
await fs.promises.writeFile(
`${finalDir}/component-info.yaml`,
yaml.stringify(componentInfo),
);
return {
resultDir: finalDir,
};
}
}
@@ -26,6 +26,7 @@ export type RunDockerContainerOptions = {
resultDir: string;
templateDir: string;
dockerClient: Docker;
createOptions?: Docker.ContainerCreateOptions;
};
/**
@@ -59,8 +60,18 @@ export const runDockerContainer = async ({
resultDir,
templateDir,
dockerClient,
createOptions = {},
}: RunDockerContainerOptions) => {
await dockerClient.pull(imageName, {});
await new Promise((resolve, reject) =>
dockerClient.pull(imageName, {}, (err, res) => {
console.warn(err, res);
if (err) {
reject(err);
} else {
resolve(res);
}
}),
);
const [{ Error: error, StatusCode: statusCode }] = await dockerClient.run(
imageName,
args,
@@ -75,6 +86,7 @@ export const runDockerContainer = async ({
`${await fs.promises.realpath(templateDir)}:/template`,
],
},
...createOptions,
},
);
@@ -17,3 +17,4 @@ export * from './cookiecutter';
export * from './types';
export * from './helpers';
export * from './templaters';
export * from './cra';
@@ -126,7 +126,7 @@ export const JobStage = ({ endedAt, startedAt, name, log, status }: Props) => {
) : (
<Suspense fallback={<LinearProgress />}>
<div style={{ height: '20vh', width: '100%' }}>
<LazyLog text={log.join('\n')} extraLines={1} />
<LazyLog text={log.join('\n')} extraLines={1} follow />
</div>
</Suspense>
)}