Merge pull request #143 from spotify/eide/cli-feedback
[cli] Add more user feedback
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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<any> => {
|
||||
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<any> => {
|
||||
'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;
|
||||
|
||||
@@ -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']);
|
||||
|
||||
+4
-1
@@ -11,6 +11,9 @@ const useStyles = makeStyles<Theme>(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"
|
||||
></Header>
|
||||
<div className={classes.pageBody}>
|
||||
<Typography variant="h3" style={{ padding: '8px 0 16px 0' }}>
|
||||
<Typography variant="h3" className={classes.title}>
|
||||
Plugin page title
|
||||
</Typography>
|
||||
<InfoCard title="Information card" maxWidth>
|
||||
|
||||
Reference in New Issue
Block a user