diff --git a/packages/kbn-plugin-generator/index.js b/packages/kbn-plugin-generator/index.js index 15adce7f01c8e5..5f20569886d887 100644 --- a/packages/kbn-plugin-generator/index.js +++ b/packages/kbn-plugin-generator/index.js @@ -44,19 +44,14 @@ exports.run = function run(argv) { # {dim Usage:} node scripts/generate-plugin {bold [name]} Generate a fresh Kibana plugin in the plugins/ directory - - # {dim Core Kibana plugins:} - node scripts/generate-plugin {bold [name]} -i - To generate a core Kibana plugin inside the src/plugins/ directory, add the -i flag. `) + '\n' ); process.exit(1); } const name = options._[0]; - const isKibanaPlugin = options.internal; const template = resolve(__dirname, './sao_template'); - const kibanaPlugins = resolve(__dirname, isKibanaPlugin ? '../../src/plugins' : '../../plugins'); + const kibanaPlugins = resolve(process.cwd(), 'plugins'); const targetPath = resolve(kibanaPlugins, snakeCase(name)); sao({ @@ -64,7 +59,6 @@ exports.run = function run(argv) { targetPath: targetPath, configOptions: { name, - isKibanaPlugin, targetPath, }, }).catch(error => { diff --git a/packages/kbn-plugin-generator/sao_template/sao.js b/packages/kbn-plugin-generator/sao_template/sao.js index aed4b9a02838f5..8e5e1061901940 100755 --- a/packages/kbn-plugin-generator/sao_template/sao.js +++ b/packages/kbn-plugin-generator/sao_template/sao.js @@ -17,7 +17,8 @@ * under the License. */ -const { relative } = require('path'); +const { relative, resolve } = require('path'); +const fs = require('fs'); const startCase = require('lodash.startcase'); const camelCase = require('lodash.camelcase'); @@ -29,9 +30,55 @@ const pkg = require('../package.json'); const kibanaPkgPath = require.resolve('../../../package.json'); const kibanaPkg = require(kibanaPkgPath); // eslint-disable-line import/no-dynamic-require -module.exports = function({ name, targetPath, isKibanaPlugin }) { +async function gitInit(dir) { + // Only plugins in /plugins get git init + try { + await execa('git', ['init', dir]); + console.log(`Git repo initialized in ${dir}`); + } catch (error) { + console.error(error); + throw new Error(`Failure to git init ${dir}: ${error.all || error}`); + } +} + +async function moveToCustomFolder(from, to) { + try { + await execa('mv', [from, to]); + } catch (error) { + console.error(error); + throw new Error(`Failure to move plugin to ${to}: ${error.all || error}`); + } +} + +async function eslintPlugin(dir) { + try { + await execa('yarn', ['lint:es', `./${dir}/**/*.ts*`, '--no-ignore', '--fix']); + } catch (error) { + console.error(error); + throw new Error(`Failure when running prettier on the generated output: ${error.all || error}`); + } +} + +module.exports = function({ name, targetPath }) { return { prompts: { + customPath: { + message: 'Would you like to create the plugin in a different folder?', + default: '/plugins', + filter(value) { + // Keep default value empty + if (value === '/plugins') return ''; + // Remove leading slash + return value.startsWith('/') ? value.slice(1) : value; + }, + validate(customPath) { + const p = resolve(process.cwd(), customPath); + const exists = fs.existsSync(p); + if (!exists) + return `Folder should exist relative to the kibana root folder. Consider /src/plugins or /x-pack/plugins.`; + return true; + }, + }, description: { message: 'Provide a short description', default: 'An awesome Kibana plugin', @@ -50,11 +97,18 @@ module.exports = function({ name, targetPath, isKibanaPlugin }) { message: 'Should a server API be generated?', default: true, }, - // generateTranslations: { - // type: 'confirm', - // message: 'Should translation files be generated?', - // default: true, - // }, + generateTranslations: { + type: 'confirm', + when: answers => { + // only for 3rd party plugins + return !answers.customPath && answers.generateApp; + }, + message: 'Should translation files be generated?', + default({ customPath }) { + // only for 3rd party plugins + return !customPath; + }, + }, generateScss: { type: 'confirm', message: 'Should SCSS be used?', @@ -64,19 +118,22 @@ module.exports = function({ name, targetPath, isKibanaPlugin }) { generateEslint: { type: 'confirm', message: 'Would you like to use a custom eslint file?', - default: !isKibanaPlugin, + default({ customPath }) { + return !customPath; + }, }, }, filters: { 'public/**/index.scss': 'generateScss', 'public/**/*': 'generateApp', 'server/**/*': 'generateApi', - // 'translations/**/*': 'generateTranslations', - // '.i18nrc.json': 'generateTranslations', + 'translations/**/*': 'generateTranslations', + 'i18nrc.json': 'generateTranslations', 'eslintrc.js': 'generateEslint', }, move: { 'eslintrc.js': '.eslintrc.js', + 'i18nrc.json': '.i18nrc.json', }, data: answers => Object.assign( @@ -86,31 +143,35 @@ module.exports = function({ name, targetPath, isKibanaPlugin }) { camelCase, snakeCase, name, - isKibanaPlugin, + // kibana plugins are placed in a the non default path + isKibanaPlugin: !answers.customPath, kbnVersion: answers.kbnVersion, upperCamelCaseName: name.charAt(0).toUpperCase() + camelCase(name).slice(1), hasUi: !!answers.generateApp, hasServer: !!answers.generateApi, hasScss: !!answers.generateScss, - relRoot: isKibanaPlugin ? '../../../..' : '../../..', + relRoot: relative( + resolve(answers.customPath || targetPath, name, 'public'), + process.cwd() + ), }, answers ), enforceNewFolder: true, installDependencies: false, - gitInit: !isKibanaPlugin, - async post({ log }) { - const dir = relative(process.cwd(), targetPath); + async post({ log, answers }) { + let dir = relative(process.cwd(), targetPath); + if (answers.customPath) { + // Move to custom path + moveToCustomFolder(targetPath, answers.customPath); + dir = relative(process.cwd(), resolve(answers.customPath, snakeCase(name))); + } else { + // Init git only in the default path + await gitInit(dir); + } // Apply eslint to the generated plugin - try { - await execa('yarn', ['lint:es', `./${dir}/**/*.ts*`, '--no-ignore', '--fix']); - } catch (error) { - console.error(error); - throw new Error( - `Failure when running prettier on the generated output: ${error.all || error}` - ); - } + eslintPlugin(dir); log.success(chalk`🎉 diff --git a/packages/kbn-plugin-generator/sao_template/sao.test.js b/packages/kbn-plugin-generator/sao_template/sao.test.js index 0dbdb7d3c097b8..03d95e12d58dac 100755 --- a/packages/kbn-plugin-generator/sao_template/sao.test.js +++ b/packages/kbn-plugin-generator/sao_template/sao.test.js @@ -23,6 +23,7 @@ const template = { fromPath: __dirname, configOptions: { name: 'Some fancy plugin', + targetPath: '', }, }; @@ -46,6 +47,7 @@ describe('plugin generator sao integration', () => { const res = await sao.mockPrompt(template, { generateApp: true, generateApi: false, + generateScss: true, }); // check output files @@ -54,6 +56,7 @@ describe('plugin generator sao integration', () => { expect(res.fileList).toContain('public/plugin.ts'); expect(res.fileList).toContain('public/types.ts'); expect(res.fileList).toContain('public/components/app.tsx'); + expect(res.fileList).toContain('public/index.scss'); expect(res.fileList).not.toContain('server/index.ts'); }); @@ -71,6 +74,20 @@ describe('plugin generator sao integration', () => { expect(res.fileList).toContain('server/routes/index.ts'); }); + it('skips eslintrc and scss', async () => { + const res = await sao.mockPrompt(template, { + generateApp: true, + generateApi: true, + generateScss: false, + generateEslint: false, + }); + + // check output files + expect(res.fileList).toContain('public/plugin.ts'); + expect(res.fileList).not.toContain('public/index.scss'); + expect(res.fileList).not.toContain('.eslintrc.js'); + }); + it('plugin package has correct title', async () => { const res = await sao.mockPrompt(template, { generateApp: true, @@ -120,5 +137,20 @@ describe('plugin generator sao integration', () => { it('includes dotfiles', async () => { const res = await sao.mockPrompt(template); expect(res.files['.eslintrc.js']).toBeTruthy(); + expect(res.files['.i18nrc.json']).toBeTruthy(); + }); + + it('validaes path override', async () => { + try { + await sao.mockPrompt(template, { + generateApp: true, + generateApi: true, + generateScss: false, + generateEslint: false, + customPath: 'banana', + }); + } catch (e) { + expect(e.message).toContain('Validation failed at prompt "customPath"'); + } }); }); diff --git a/packages/kbn-plugin-generator/sao_template/template/i18nrc.json b/packages/kbn-plugin-generator/sao_template/template/i18nrc.json new file mode 100644 index 00000000000000..a4b78b88e64e22 --- /dev/null +++ b/packages/kbn-plugin-generator/sao_template/template/i18nrc.json @@ -0,0 +1,10 @@ +{ + "prefix": "<%= camelCase(name) %>", + "paths": { + "<%= camelCase(name) %>": "." + }, + "translations": [ + "translations/ja-JP.json" + ] + } + \ No newline at end of file diff --git a/packages/kbn-plugin-generator/sao_template/template/translations/ja-JP.json b/packages/kbn-plugin-generator/sao_template/template/translations/ja-JP.json new file mode 100644 index 00000000000000..ab4503f2c129c2 --- /dev/null +++ b/packages/kbn-plugin-generator/sao_template/template/translations/ja-JP.json @@ -0,0 +1,82 @@ + +{ + "formats": { + "number": { + "currency": { + "style": "currency" + }, + "percent": { + "style": "percent" + } + }, + "date": { + "short": { + "month": "numeric", + "day": "numeric", + "year": "2-digit" + }, + "medium": { + "month": "short", + "day": "numeric", + "year": "numeric" + }, + "long": { + "month": "long", + "day": "numeric", + "year": "numeric" + }, + "full": { + "weekday": "long", + "month": "long", + "day": "numeric", + "year": "numeric" + } + }, + "time": { + "short": { + "hour": "numeric", + "minute": "numeric" + }, + "medium": { + "hour": "numeric", + "minute": "numeric", + "second": "numeric" + }, + "long": { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short" + }, + "full": { + "hour": "numeric", + "minute": "numeric", + "second": "numeric", + "timeZoneName": "short" + } + }, + "relative": { + "years": { + "units": "year" + }, + "months": { + "units": "month" + }, + "days": { + "units": "day" + }, + "hours": { + "units": "hour" + }, + "minutes": { + "units": "minute" + }, + "seconds": { + "units": "second" + } + } + }, + "messages": { + "<%= camelCase(name) %>.buttonText": "Translate me to Japanese", + } +} \ No newline at end of file