Skip to content

Commit

Permalink
Allow custom paths in plugin generator (#57766) (#58342)
Browse files Browse the repository at this point in the history
* Allow custom paths

* Add translation file

* Fix jest test

* Added more tests

* Update docs
Content of translation jsons

* Leave only one translation file

* generate default translations file

* Simplify i18n setup

* Code review changes

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
  • Loading branch information
Liza Katz and elasticmachine committed Feb 24, 2020
1 parent c5727c1 commit ea14b97
Show file tree
Hide file tree
Showing 5 changed files with 209 additions and 30 deletions.
8 changes: 1 addition & 7 deletions packages/kbn-plugin-generator/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,27 +44,21 @@ 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({
template: template,
targetPath: targetPath,
configOptions: {
name,
isKibanaPlugin,
targetPath,
},
}).catch(error => {
Expand Down
107 changes: 84 additions & 23 deletions packages/kbn-plugin-generator/sao_template/sao.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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',
Expand All @@ -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?',
Expand All @@ -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(
Expand All @@ -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`🎉
Expand Down
32 changes: 32 additions & 0 deletions packages/kbn-plugin-generator/sao_template/sao.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const template = {
fromPath: __dirname,
configOptions: {
name: 'Some fancy plugin',
targetPath: '',
},
};

Expand All @@ -46,6 +47,7 @@ describe('plugin generator sao integration', () => {
const res = await sao.mockPrompt(template, {
generateApp: true,
generateApi: false,
generateScss: true,
});

// check output files
Expand All @@ -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');
});

Expand All @@ -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,
Expand Down Expand Up @@ -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"');
}
});
});
10 changes: 10 additions & 0 deletions packages/kbn-plugin-generator/sao_template/template/i18nrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"prefix": "<%= camelCase(name) %>",
"paths": {
"<%= camelCase(name) %>": "."
},
"translations": [
"translations/ja-JP.json"
]
}

Original file line number Diff line number Diff line change
@@ -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",
}
}

0 comments on commit ea14b97

Please sign in to comment.