diff --git a/frontend/packages/cli/package.json b/frontend/packages/cli/package.json index a903abdcd5..e2a24a6214 100644 --- a/frontend/packages/cli/package.json +++ b/frontend/packages/cli/package.json @@ -6,6 +6,7 @@ "license": "Apache-2.0", "private": false, "scripts": { + "exec": "npx ts-node ./src", "build": "web-scripts build", "lint": "web-scripts lint", "test": "web-scripts test", diff --git a/frontend/packages/cli/src/commands/createPlugin.ts b/frontend/packages/cli/src/commands/createPlugin.ts index fc044dfc9a..06d1e0c47e 100644 --- a/frontend/packages/cli/src/commands/createPlugin.ts +++ b/frontend/packages/cli/src/commands/createPlugin.ts @@ -1,6 +1,7 @@ import fs from 'fs-extra'; import path from 'path'; import handlebars from 'handlebars'; +import chalk from 'chalk'; import inquirer, { Answers, Question } from 'inquirer'; import recursive from 'recursive-readdir'; @@ -9,11 +10,16 @@ export const createPluginFolder = (rootDir: string, id: string): string => { if (fs.existsSync(destination)) { throw new Error( - `A plugin with the same name already exists: ${destination}`, + `A plugin with the same name already exists: ${chalk.cyan( + destination.replace(rootDir, ''), + )}\nPlease try again with a different Plugin ID`, ); } try { + console.log( + chalk.green(`Creating:\t${chalk.cyan(destination.replace(rootDir, ''))}`), + ); fs.mkdirSync(destination, { recursive: true }); return destination; } catch (e) { @@ -35,6 +41,9 @@ export const createFileFromTemplate = ( ...answers, }); try { + console.log( + chalk.green(`Creating:\t${chalk.cyan(path.basename(destination))}`), + ); fs.writeFileSync(destination, contents); } catch (e) { throw new Error(`Failed to create file: ${destination}: ${e.message}`); @@ -66,27 +75,68 @@ export const createFromTemplateDir = async ( answers, ); } else { - fs.copyFileSync(file, file.replace(templateFolder, destinationFolder)); + console.log(chalk.green(`Copying:\t${chalk.cyan(path.basename(file))}`)); + try { + fs.copyFileSync(file, file.replace(templateFolder, destinationFolder)); + } catch (e) { + throw new Error( + `Failed to copy file: ${file.replace( + templateFolder, + destinationFolder, + )}: ${e.message}`, + ); + } } }); }; +const cleanUp = async (rootDir: string, id: string) => { + const destination = path.join(rootDir, 'packages', 'plugins', id); + + const questions: Question[] = [ + { + type: 'confirm', + name: 'cleanup', + message: chalk.yellow( + `Do you want to remove the created directory and all the files in it?\ndir: ${chalk.cyan( + destination, + )}`, + ), + }, + ]; + const answers: Answers = await inquirer.prompt(questions); + + if (answers.cleanup) { + try { + // Not using recursion here, so only empty directories can be removed + fs.rmdirSync(destination); + console.log(); + console.log( + chalk.green(`Removing ${chalk.cyan(destination.replace(rootDir, ''))}`), + ); + console.log(); + } catch (e) { + console.log(); + console.log(chalk.red(`Failed to cleanup: ${e.message}`)); + console.log(); + } + } +}; + const createPlugin = async (): Promise => { - const currentDir = process.argv[1]; const questions: Question[] = [ { type: 'input', name: 'id', - message: 'Enter an ID for the plugin [required]', + message: chalk.blue('Enter an ID for the plugin [required]'), validate: (value: any) => - value ? true : 'Please enter an ID for the plugin', + value ? true : chalk.red('Please enter an ID for the plugin'), }, ]; const answers: Answers = await inquirer.prompt(questions); - const destinationFolder = createPluginFolder( - path.join(currentDir, '..', '..', '..'), - answers.id, - ); + + const currentDir = process.argv[1]; + const rootDir = path.join(currentDir, '..', '..', '..'); const templateFolder = path.join( currentDir, '..', @@ -97,15 +147,34 @@ const createPlugin = async (): Promise => { 'default-plugin', ); - await createFromTemplateDir(templateFolder, destinationFolder, answers); + try { + const destinationFolder = createPluginFolder(rootDir, answers.id); + await createFromTemplateDir(templateFolder, destinationFolder, answers); - console.log( - `✨ You have created a Backstage Plugin packages/plugins/${answers.id}`, - ); - console.log(''); - console.log('Run yarn start in the plugin directory to start it'); + console.log(); + console.log( + chalk.green( + `Successfully created a Backstage Plugin in ${chalk.cyan( + path.join('packages', 'plugins', answers.id), + )}`, + ), + ); - return destinationFolder; + console.log( + chalk.green( + `Run ${chalk.cyan('yarn start')} in the plugin directory to start it.`, + ), + ); + console.log(); + + return destinationFolder; + } catch (e) { + console.log(); + console.log(chalk.red(e.message)); + console.log(); + + await cleanUp(rootDir, answers.id); + } }; export default createPlugin; diff --git a/frontend/packages/cli/src/index.ts b/frontend/packages/cli/src/index.ts index ade0f9276d..2c0f6c0ce6 100644 --- a/frontend/packages/cli/src/index.ts +++ b/frontend/packages/cli/src/index.ts @@ -1,4 +1,6 @@ import program from 'commander'; +import chalk from 'chalk'; +import fs from 'fs'; import createPluginCommand from './commands/createPlugin'; import watch from './commands/watch-deps'; import serve from './commands/serve'; @@ -8,6 +10,10 @@ process.on('unhandledRejection', err => { }); const main = (argv: string[]) => { + const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8')); + + program.name('backstage-cli').version(packageJson.version ?? '0.0.0'); + program .command('create-plugin') .description('Creates a new plugin in the current repository') @@ -24,16 +30,21 @@ const main = (argv: string[]) => { .action(watch); program.on('command:*', () => { - // eslint-disable-next-line no-console - console.error( - 'Invalid command: %s\nSee --help for a list of available commands.', - program.args.join(' '), + console.log(); + console.log( + chalk.red(`Invalid command: ${chalk.cyan(program.args.join(' '))}`), ); + console.log(chalk.red('See --help for a list of available commands.')); + console.log(); process.exit(1); }); + if (!process.argv.slice(2).length) { + program.outputHelp(chalk.yellow); + } + program.parse(argv); }; main(process.argv); -// main([process.argv[0], process.argv[1], 'create-plugin']); +// main([process.argv[0], process.argv[1], '--version']); diff --git a/frontend/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.tsx.hbs b/frontend/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.tsx.hbs index f5a45ae6e4..e2a1e612cc 100644 --- a/frontend/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.tsx.hbs +++ b/frontend/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.tsx.hbs @@ -11,6 +11,9 @@ const useStyles = makeStyles(theme => ({ pageBody: { padding: theme.spacing(3), }, + title: { + padding: theme.spacing(1,0,2,0), + }, })); const ExampleComponent: FC<{}> = () => { @@ -23,7 +26,7 @@ const ExampleComponent: FC<{}> = () => { subtitle="Optional subtitle" >
- + Plugin page title