Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow custom paths in plugin generator #57766

Merged
merged 16 commits into from
Feb 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
{
Bamieh marked this conversation as resolved.
Show resolved Hide resolved
"prefix": "<%= camelCase(name) %>",
"paths": {
"<%= camelCase(name) %>": "."
},
"translations": [
"translations/ja-JP.json"
lizozom marked this conversation as resolved.
Show resolved Hide resolved
]
}

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",
}
}