From 39cd481040d4277c96bc744b6459b2c07f6411bd Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Tue, 3 Mar 2020 10:12:18 +0100 Subject: [PATCH 1/7] Add chalked output to main --- frontend/packages/cli/src/index.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/frontend/packages/cli/src/index.ts b/frontend/packages/cli/src/index.ts index ade0f9276d..95081e63ad 100644 --- a/frontend/packages/cli/src/index.ts +++ b/frontend/packages/cli/src/index.ts @@ -1,4 +1,5 @@ import program from 'commander'; +import chalk from 'chalk'; import createPluginCommand from './commands/createPlugin'; import watch from './commands/watch-deps'; import serve from './commands/serve'; @@ -24,14 +25,19 @@ 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); }; From 2c9db372756816dca2a616578f99fd30f260adaf Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Tue, 3 Mar 2020 10:13:59 +0100 Subject: [PATCH 2/7] Add name and version to main --- frontend/packages/cli/src/index.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/frontend/packages/cli/src/index.ts b/frontend/packages/cli/src/index.ts index 95081e63ad..de517cc6b8 100644 --- a/frontend/packages/cli/src/index.ts +++ b/frontend/packages/cli/src/index.ts @@ -1,5 +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'; @@ -9,6 +10,15 @@ process.on('unhandledRejection', err => { }); const main = (argv: string[]) => { + const version = fs + .readFileSync('package.json', 'utf-8') + .split('\n') + .filter(row => row.match(/"version":/)) + .join() + .match(/"version":\s"(?\d\.\d\.\d)"/)?.groups?.version; + + program.name('backstage-cli').version(version ?? '0.0.0'); + program .command('create-plugin') .description('Creates a new plugin in the current repository') From 2c41afdedf139a886546c7cb4a52e464fb505e6a Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Tue, 3 Mar 2020 12:41:02 +0100 Subject: [PATCH 3/7] Add script to run the cli directly --- frontend/packages/cli/package.json | 1 + 1 file changed, 1 insertion(+) 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", From e390ddbceba1ce958e27c187cf7ce4d31341df31 Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Tue, 3 Mar 2020 12:41:54 +0100 Subject: [PATCH 4/7] Add styles for template that otherwise was interpreted as a hb var --- .../src/components/ExampleComponent/ExampleComponent.tsx.hbs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 From 8b967badaca260d2678473afc60bbebb28d1b4a8 Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Tue, 3 Mar 2020 12:43:01 +0100 Subject: [PATCH 5/7] Add more feedback as to what is happening when creating a plugin --- .../packages/cli/src/commands/createPlugin.ts | 101 ++++++++++++------ 1 file changed, 68 insertions(+), 33 deletions(-) diff --git a/frontend/packages/cli/src/commands/createPlugin.ts b/frontend/packages/cli/src/commands/createPlugin.ts index fc044dfc9a..b4277b1fc3 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,46 +75,72 @@ 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 createPlugin = async (): Promise => { - const currentDir = process.argv[1]; - const questions: Question[] = [ - { - type: 'input', - name: 'id', - message: 'Enter an ID for the plugin [required]', - validate: (value: any) => - value ? true : 'Please enter an ID for the plugin', - }, - ]; - const answers: Answers = await inquirer.prompt(questions); - const destinationFolder = createPluginFolder( - path.join(currentDir, '..', '..', '..'), - answers.id, - ); - const templateFolder = path.join( - currentDir, - '..', - '..', - '@spotify-backstage', - 'cli', - 'templates', - 'default-plugin', - ); + try { + const currentDir = process.argv[1]; + const questions: Question[] = [ + { + type: 'input', + name: 'id', + message: chalk.blue('Enter an ID for the plugin [required]'), + validate: (value: any) => + 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 templateFolder = path.join( + currentDir, + '..', + '..', + '@spotify-backstage', + 'cli', + 'templates', + 'default-plugin', + ); - await createFromTemplateDir(templateFolder, destinationFolder, answers); + 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(e.message); + console.log(); + } }; export default createPlugin; From 79ef90713229b5289a259ac20e833a02c44c3a11 Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Tue, 3 Mar 2020 12:49:31 +0100 Subject: [PATCH 6/7] Use json.parse for version instead --- frontend/packages/cli/src/index.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/frontend/packages/cli/src/index.ts b/frontend/packages/cli/src/index.ts index de517cc6b8..2c0f6c0ce6 100644 --- a/frontend/packages/cli/src/index.ts +++ b/frontend/packages/cli/src/index.ts @@ -10,14 +10,9 @@ process.on('unhandledRejection', err => { }); const main = (argv: string[]) => { - const version = fs - .readFileSync('package.json', 'utf-8') - .split('\n') - .filter(row => row.match(/"version":/)) - .join() - .match(/"version":\s"(?\d\.\d\.\d)"/)?.groups?.version; + const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8')); - program.name('backstage-cli').version(version ?? '0.0.0'); + program.name('backstage-cli').version(packageJson.version ?? '0.0.0'); program .command('create-plugin') @@ -52,4 +47,4 @@ const main = (argv: string[]) => { }; main(process.argv); -// main([process.argv[0], process.argv[1], 'create-plugin']); +// main([process.argv[0], process.argv[1], '--version']); From 961c2ccdeaa71acd3f5db961f0a90a5ba5edbc90 Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Tue, 3 Mar 2020 13:35:14 +0100 Subject: [PATCH 7/7] Optionally clean up failed plugin directory --- .../packages/cli/src/commands/createPlugin.ts | 90 +++++++++++++------ 1 file changed, 62 insertions(+), 28 deletions(-) diff --git a/frontend/packages/cli/src/commands/createPlugin.ts b/frontend/packages/cli/src/commands/createPlugin.ts index b4277b1fc3..06d1e0c47e 100644 --- a/frontend/packages/cli/src/commands/createPlugin.ts +++ b/frontend/packages/cli/src/commands/createPlugin.ts @@ -12,7 +12,7 @@ export const createPluginFolder = (rootDir: string, id: string): string => { throw new Error( `A plugin with the same name already exists: ${chalk.cyan( destination.replace(rootDir, ''), - )}\nPlease try again with a different plugin Id`, + )}\nPlease try again with a different Plugin ID`, ); } @@ -90,33 +90,65 @@ export const createFromTemplateDir = async ( }); }; -const createPlugin = async (): Promise => { - try { - const currentDir = process.argv[1]; - const questions: Question[] = [ - { - type: 'input', - name: 'id', - message: chalk.blue('Enter an ID for the plugin [required]'), - validate: (value: any) => - 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 templateFolder = path.join( - currentDir, - '..', - '..', - '@spotify-backstage', - 'cli', - 'templates', - 'default-plugin', - ); +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 questions: Question[] = [ + { + type: 'input', + name: 'id', + message: chalk.blue('Enter an ID for the plugin [required]'), + validate: (value: any) => + value ? true : chalk.red('Please enter an ID for the plugin'), + }, + ]; + const answers: Answers = await inquirer.prompt(questions); + + const currentDir = process.argv[1]; + const rootDir = path.join(currentDir, '..', '..', '..'); + const templateFolder = path.join( + currentDir, + '..', + '..', + '@spotify-backstage', + 'cli', + 'templates', + 'default-plugin', + ); + + try { + const destinationFolder = createPluginFolder(rootDir, answers.id); await createFromTemplateDir(templateFolder, destinationFolder, answers); console.log(); @@ -138,8 +170,10 @@ const createPlugin = async (): Promise => { return destinationFolder; } catch (e) { console.log(); - console.log(e.message); + console.log(chalk.red(e.message)); console.log(); + + await cleanUp(rootDir, answers.id); } };