create.ts 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. import commandExists from 'command-exists';
  2. import { readFileSync, promises as fs } from 'fs';
  3. import { prompt } from 'inquirer';
  4. import kebabCase from 'lodash/kebabCase';
  5. import path from 'path';
  6. import gitPromise from 'simple-git/promise';
  7. import { useSpinner } from '../../utils/useSpinner';
  8. import { rmdir } from '../../utils/rmdir';
  9. import { promptInput, promptConfirm } from '../../utils/prompt';
  10. import chalk from 'chalk';
  11. const simpleGit = gitPromise(process.cwd());
  12. interface PluginDetails {
  13. name: string;
  14. org: string;
  15. description: string;
  16. author: boolean | string;
  17. url: string;
  18. keywords: string;
  19. }
  20. type PluginType = 'angular-panel' | 'react-panel' | 'datasource-plugin';
  21. const RepositoriesPaths = {
  22. 'angular-panel': 'git@github.com:grafana/simple-angular-panel.git',
  23. 'react-panel': 'git@github.com:grafana/simple-react-panel.git',
  24. 'datasource-plugin': 'git@github.com:grafana/simple-datasource.git',
  25. };
  26. export const getGitUsername = async () => await simpleGit.raw(['config', '--global', 'user.name']);
  27. export const getPluginIdFromName = (name: string) => kebabCase(name);
  28. export const getPluginId = (pluginDetails: PluginDetails) =>
  29. `${kebabCase(pluginDetails.org)}-${getPluginIdFromName(pluginDetails.name)}`;
  30. export const getPluginKeywords = (pluginDetails: PluginDetails) =>
  31. pluginDetails.keywords
  32. .split(',')
  33. .map(k => k.trim())
  34. .filter(k => k !== '');
  35. export const verifyGitExists = async () => {
  36. return new Promise((resolve, reject) => {
  37. commandExists('git', (err, exists) => {
  38. if (exists) {
  39. resolve(true);
  40. }
  41. reject(new Error('git is not installed'));
  42. });
  43. });
  44. };
  45. export const promptPluginType = async () =>
  46. prompt<{ type: PluginType }>([
  47. {
  48. type: 'list',
  49. message: 'Select plugin type',
  50. name: 'type',
  51. choices: [
  52. { name: 'Angular panel', value: 'angular-panel' },
  53. { name: 'React panel', value: 'react-panel' },
  54. { name: 'Datasource plugin', value: 'datasource-plugin' },
  55. ],
  56. },
  57. ]);
  58. export const promptPluginDetails = async (name?: string) => {
  59. const username = (await getGitUsername()).trim();
  60. const responses = await prompt<PluginDetails>([
  61. promptInput('name', 'Plugin name', true, name),
  62. promptInput('org', 'Organization (used as part of plugin ID)', true),
  63. promptInput('description', 'Description'),
  64. promptInput('keywords', 'Keywords (separated by comma)'),
  65. // Try using git specified username
  66. promptConfirm('author', `Author (${username})`, username, username !== ''),
  67. // Prompt for manual author entry if no git user.name specifed
  68. promptInput('author', `Author`, true, undefined, answers => !answers.author || username === ''),
  69. promptInput('url', 'Your URL (i.e. organisation url)'),
  70. ]);
  71. return {
  72. ...responses,
  73. author: responses.author === true ? username : responses.author,
  74. };
  75. };
  76. export const fetchTemplate = useSpinner<{ type: PluginType; dest: string }>(
  77. 'Fetching plugin template...',
  78. async ({ type, dest }) => {
  79. const url = RepositoriesPaths[type];
  80. if (!url) {
  81. throw new Error('Unknown plugin type');
  82. }
  83. await simpleGit.clone(url, dest);
  84. }
  85. );
  86. export const prepareJsonFiles = useSpinner<{ pluginDetails: PluginDetails; pluginPath: string }>(
  87. 'Saving package.json and plugin.json files',
  88. async ({ pluginDetails, pluginPath }) => {
  89. const packageJsonPath = path.resolve(pluginPath, 'package.json');
  90. const pluginJsonPath = path.resolve(pluginPath, 'src/plugin.json');
  91. const packageJson: any = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
  92. const pluginJson: any = JSON.parse(readFileSync(pluginJsonPath, 'utf8'));
  93. const pluginId = `${kebabCase(pluginDetails.org)}-${getPluginIdFromName(pluginDetails.name)}`;
  94. packageJson.name = pluginId;
  95. packageJson.author = pluginDetails.author;
  96. packageJson.description = pluginDetails.description;
  97. pluginJson.name = pluginDetails.name;
  98. pluginJson.id = pluginId;
  99. pluginJson.info = {
  100. ...pluginJson.info,
  101. description: pluginDetails.description,
  102. author: {
  103. name: pluginDetails.author,
  104. url: pluginDetails.url,
  105. },
  106. keywords: getPluginKeywords(pluginDetails),
  107. };
  108. await Promise.all(
  109. [packageJson, pluginJson].map((f, i) => {
  110. const filePath = i === 0 ? packageJsonPath : pluginJsonPath;
  111. return fs.writeFile(filePath, JSON.stringify(f, null, 2));
  112. })
  113. );
  114. }
  115. );
  116. export const removeGitFiles = useSpinner('Cleaning', async pluginPath => rmdir(`${path.resolve(pluginPath, '.git')}`));
  117. export const formatPluginDetails = (details: PluginDetails) => {
  118. console.group();
  119. console.log();
  120. console.log(chalk.bold.yellow('Your plugin details'));
  121. console.log('---');
  122. console.log(chalk.bold('Name: '), details.name);
  123. console.log(chalk.bold('ID: '), getPluginId(details));
  124. console.log(chalk.bold('Description: '), details.description);
  125. console.log(chalk.bold('Keywords: '), getPluginKeywords(details));
  126. console.log(chalk.bold('Author: '), details.author);
  127. console.log(chalk.bold('Organisation: '), details.org);
  128. console.log(chalk.bold('Website: '), details.url);
  129. console.log();
  130. console.groupEnd();
  131. };