mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[kbn/plugin-generator] remove sao, modernize (#75465)
Co-authored-by: spalger <spalger@users.noreply.github.com>
This commit is contained in:
parent
958296c5c2
commit
7b23e7cd8b
52 changed files with 4128 additions and 4529 deletions
|
@ -41,7 +41,7 @@ target
|
|||
# package overrides
|
||||
/packages/eslint-config-kibana
|
||||
/packages/kbn-interpreter/src/common/lib/grammar.js
|
||||
/packages/kbn-plugin-generator/sao_template/template
|
||||
/packages/kbn-plugin-generator/template
|
||||
/packages/kbn-pm/dist
|
||||
/packages/kbn-test/src/functional_test_runner/__tests__/fixtures/
|
||||
/packages/kbn-test/src/functional_test_runner/lib/config/__tests__/fixtures/
|
||||
|
|
|
@ -604,7 +604,6 @@ module.exports = {
|
|||
{
|
||||
files: [
|
||||
'.eslintrc.js',
|
||||
'packages/kbn-plugin-generator/**/*.js',
|
||||
'packages/kbn-eslint-import-resolver-kibana/**/*.js',
|
||||
'packages/kbn-eslint-plugin-eslint/**/*',
|
||||
'x-pack/gulpfile.js',
|
||||
|
|
|
@ -17,17 +17,17 @@ If you are targeting **Kibana 6.3 or greater** then checkout the corresponding K
|
|||
To target the current development version of Kibana just use the default `master` branch.
|
||||
|
||||
```sh
|
||||
node scripts/generate_plugin my_plugin_name
|
||||
node scripts/generate_plugin --name my_plugin_name -y
|
||||
# generates a plugin in `plugins/my_plugin_name`
|
||||
```
|
||||
|
||||
To target 6.3, use the `6.x` branch (until the `6.3` branch is created).
|
||||
To target 6.8, use the `6.8` branch.
|
||||
|
||||
```sh
|
||||
git checkout 6.x
|
||||
yarn kbn bootstrap # always bootstrap when switching branches
|
||||
node scripts/generate_plugin my_plugin_name
|
||||
# generates a plugin for Kibana 6.3 in `../kibana-extra/my_plugin_name`
|
||||
node scripts/generate_plugin --name my_plugin_name -y
|
||||
# generates a plugin for Kibana 6.8 in `../kibana-extra/my_plugin_name`
|
||||
```
|
||||
|
||||
The generate script supports a few flags; run it with the `--help` flag to learn more.
|
||||
|
@ -49,7 +49,7 @@ yarn kbn bootstrap
|
|||
|
||||
## Plugin Development Scripts
|
||||
|
||||
Generated plugins receive a handful of scripts that can be used during development. Those scripts are detailed in the [README.md](sao_template/template/README.md) file in each newly generated plugin, and expose the scripts provided by the [Kibana plugin helpers](../kbn-plugin-helpers), but here is a quick reference in case you need it:
|
||||
Generated plugins receive a handful of scripts that can be used during development. Those scripts are detailed in the [README.md](template/README.md) file in each newly generated plugin, and expose the scripts provided by the [Kibana plugin helpers](../kbn-plugin-helpers), but here is a quick reference in case you need it:
|
||||
|
||||
> ***NOTE:*** All of these scripts should be run from the generated plugin.
|
||||
|
||||
|
|
|
@ -1,69 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
const { resolve } = require('path');
|
||||
|
||||
const dedent = require('dedent');
|
||||
const sao = require('sao');
|
||||
const chalk = require('chalk');
|
||||
const getopts = require('getopts');
|
||||
const { snakeCase } = require('lodash');
|
||||
|
||||
exports.run = function run(argv) {
|
||||
const options = getopts(argv, {
|
||||
alias: {
|
||||
h: 'help',
|
||||
i: 'internal',
|
||||
},
|
||||
});
|
||||
|
||||
if (!options.help && options._.length !== 1) {
|
||||
console.log(chalk`{red {bold [name]} is a required argument}\n`);
|
||||
options.help = true;
|
||||
}
|
||||
|
||||
if (options.help) {
|
||||
console.log(
|
||||
dedent(chalk`
|
||||
# {dim Usage:}
|
||||
node scripts/generate-plugin {bold [name]}
|
||||
Generate a fresh Kibana plugin in the plugins/ directory
|
||||
`) + '\n'
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const name = options._[0];
|
||||
const template = resolve(__dirname, './sao_template');
|
||||
const kibanaPlugins = resolve(process.cwd(), 'plugins');
|
||||
const targetPath = resolve(kibanaPlugins, snakeCase(name));
|
||||
|
||||
sao({
|
||||
template: template,
|
||||
targetPath: targetPath,
|
||||
configOptions: {
|
||||
name,
|
||||
targetPath,
|
||||
},
|
||||
}).catch((error) => {
|
||||
console.error(chalk`{red fatal error}!`);
|
||||
console.error(error.stack);
|
||||
process.exit(1);
|
||||
});
|
||||
};
|
|
@ -1,62 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { spawn } from 'child_process';
|
||||
import Fs from 'fs';
|
||||
import { resolve } from 'path';
|
||||
import { promisify } from 'util';
|
||||
|
||||
import del from 'del';
|
||||
import { snakeCase } from 'lodash';
|
||||
|
||||
const statAsync = promisify(Fs.stat);
|
||||
const ROOT_DIR = resolve(__dirname, '../../../');
|
||||
|
||||
const pluginName = 'ispec-plugin';
|
||||
const snakeCased = snakeCase(pluginName);
|
||||
const generatedPath = resolve(ROOT_DIR, `plugins/${snakeCased}`);
|
||||
|
||||
beforeAll(async () => {
|
||||
await del(generatedPath, { force: true });
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await del(generatedPath, { force: true });
|
||||
});
|
||||
|
||||
it('generates a plugin', async () => {
|
||||
await new Promise((resolve, reject) => {
|
||||
const proc = spawn(process.execPath, ['scripts/generate_plugin.js', pluginName], {
|
||||
cwd: ROOT_DIR,
|
||||
stdio: 'pipe',
|
||||
});
|
||||
|
||||
proc.stdout.on('data', function selectDefaults() {
|
||||
proc.stdin.write('\n'); // Generate a plugin with default options.
|
||||
});
|
||||
|
||||
proc.on('close', resolve);
|
||||
proc.on('error', reject);
|
||||
});
|
||||
|
||||
const stats = await statAsync(generatedPath);
|
||||
if (!stats.isDirectory()) {
|
||||
throw new Error(`Expected [${generatedPath}] to be a directory`);
|
||||
}
|
||||
});
|
|
@ -1,14 +1,26 @@
|
|||
{
|
||||
"name": "@kbn/plugin-generator",
|
||||
"license": "Apache-2.0",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"main": "target/index.js",
|
||||
"scripts": {
|
||||
"kbn:bootstrap": "node scripts/build",
|
||||
"kbn:watch": "node scripts/build --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": "^4.1.0",
|
||||
"dedent": "^0.7.0",
|
||||
"@kbn/dev-utils": "1.0.0",
|
||||
"ejs": "^3.1.5",
|
||||
"execa": "^4.0.2",
|
||||
"getopts": "^2.2.4",
|
||||
"lodash": "^4.17.15",
|
||||
"sao": "^0.22.12"
|
||||
"inquirer": "^7.3.3",
|
||||
"normalize-path": "^3.0.0",
|
||||
"prettier": "^2.0.5",
|
||||
"vinyl": "^2.2.0",
|
||||
"vinyl-fs": "^3.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ejs": "^3.0.4",
|
||||
"@types/prettier": "^2.0.2",
|
||||
"@types/inquirer": "^7.3.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,189 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
const { relative, resolve } = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const { camelCase, startCase, snakeCase } = require('lodash');
|
||||
const chalk = require('chalk');
|
||||
const execa = require('execa');
|
||||
|
||||
const pkg = require('../package.json');
|
||||
const kibanaPkgPath = require.resolve('../../../package.json');
|
||||
const kibanaPkg = require(kibanaPkgPath); // eslint-disable-line import/no-dynamic-require
|
||||
|
||||
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',
|
||||
},
|
||||
kbnVersion: {
|
||||
message: 'What Kibana version are you targeting?',
|
||||
default: kibanaPkg.version,
|
||||
},
|
||||
generateApp: {
|
||||
type: 'confirm',
|
||||
message: 'Should an app component be generated?',
|
||||
default: true,
|
||||
},
|
||||
generateApi: {
|
||||
type: 'confirm',
|
||||
message: 'Should a server API 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?',
|
||||
when: (answers) => answers.generateApp,
|
||||
default: true,
|
||||
},
|
||||
generateEslint: {
|
||||
type: 'confirm',
|
||||
message: 'Would you like to use a custom eslint file?',
|
||||
default({ customPath }) {
|
||||
return !customPath;
|
||||
},
|
||||
},
|
||||
generateTsconfig: {
|
||||
type: 'confirm',
|
||||
message: 'Would you like to use a custom tsconfig file?',
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
filters: {
|
||||
'public/**/index.scss': 'generateScss',
|
||||
'public/**/*': 'generateApp',
|
||||
'server/**/*': 'generateApi',
|
||||
'translations/**/*': 'generateTranslations',
|
||||
'i18nrc.json': 'generateTranslations',
|
||||
'eslintrc.js': 'generateEslint',
|
||||
'tsconfig.json': 'generateTsconfig',
|
||||
},
|
||||
move: {
|
||||
'eslintrc.js': '.eslintrc.js',
|
||||
'i18nrc.json': '.i18nrc.json',
|
||||
},
|
||||
data: (answers) => {
|
||||
const pathToPlugin = answers.customPath
|
||||
? resolve(answers.customPath, camelCase(name), 'public')
|
||||
: resolve(targetPath, 'public');
|
||||
return Object.assign(
|
||||
{
|
||||
templateVersion: pkg.version,
|
||||
startCase,
|
||||
camelCase,
|
||||
snakeCase,
|
||||
name,
|
||||
// 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: relative(pathToPlugin, process.cwd()),
|
||||
},
|
||||
answers
|
||||
);
|
||||
},
|
||||
enforceNewFolder: true,
|
||||
installDependencies: false,
|
||||
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
|
||||
eslintPlugin(dir);
|
||||
|
||||
log.success(chalk`🎉
|
||||
|
||||
Your plugin has been created in {bold ${dir}}.
|
||||
|
||||
{bold yarn start}
|
||||
`);
|
||||
},
|
||||
};
|
||||
};
|
|
@ -1,159 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
const sao = require('sao');
|
||||
|
||||
const template = {
|
||||
fromPath: __dirname,
|
||||
configOptions: {
|
||||
name: 'Some fancy plugin',
|
||||
targetPath: '',
|
||||
},
|
||||
};
|
||||
|
||||
function getFileContents(file) {
|
||||
return file.contents.toString();
|
||||
}
|
||||
|
||||
describe('plugin generator sao integration', () => {
|
||||
test('skips files when answering no', async () => {
|
||||
const res = await sao.mockPrompt(template, {
|
||||
generateApp: false,
|
||||
generateApi: false,
|
||||
});
|
||||
|
||||
expect(res.fileList).toContain('common/index.ts');
|
||||
expect(res.fileList).not.toContain('public/index.ts');
|
||||
expect(res.fileList).not.toContain('server/index.ts');
|
||||
});
|
||||
|
||||
it('includes app when answering yes', async () => {
|
||||
const res = await sao.mockPrompt(template, {
|
||||
generateApp: true,
|
||||
generateApi: false,
|
||||
generateScss: true,
|
||||
});
|
||||
|
||||
// check output files
|
||||
expect(res.fileList).toContain('common/index.ts');
|
||||
expect(res.fileList).toContain('public/index.ts');
|
||||
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');
|
||||
});
|
||||
|
||||
it('includes server api when answering yes', async () => {
|
||||
const res = await sao.mockPrompt(template, {
|
||||
generateApp: true,
|
||||
generateApi: true,
|
||||
});
|
||||
|
||||
// check output files
|
||||
expect(res.fileList).toContain('public/plugin.ts');
|
||||
expect(res.fileList).toContain('server/plugin.ts');
|
||||
expect(res.fileList).toContain('server/index.ts');
|
||||
expect(res.fileList).toContain('server/types.ts');
|
||||
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,
|
||||
generateTsconfig: 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');
|
||||
expect(res.fileList).not.toContain('tsconfig.json');
|
||||
});
|
||||
|
||||
it('plugin package has correct title', async () => {
|
||||
const res = await sao.mockPrompt(template, {
|
||||
generateApp: true,
|
||||
generateApi: true,
|
||||
});
|
||||
|
||||
const contents = getFileContents(res.files['common/index.ts']);
|
||||
const controllerLine = contents.match("PLUGIN_NAME = '(.*)'")[1];
|
||||
|
||||
expect(controllerLine).toContain('Some fancy plugin');
|
||||
});
|
||||
|
||||
it('package has version "kibana" with master', async () => {
|
||||
const res = await sao.mockPrompt(template, {
|
||||
kbnVersion: 'master',
|
||||
});
|
||||
|
||||
const packageContents = getFileContents(res.files['kibana.json']);
|
||||
const pkg = JSON.parse(packageContents);
|
||||
|
||||
expect(pkg.version).toBe('master');
|
||||
});
|
||||
|
||||
it('package has correct version', async () => {
|
||||
const res = await sao.mockPrompt(template, {
|
||||
kbnVersion: 'v6.0.0',
|
||||
});
|
||||
|
||||
const packageContents = getFileContents(res.files['kibana.json']);
|
||||
const pkg = JSON.parse(packageContents);
|
||||
|
||||
expect(pkg.version).toBe('v6.0.0');
|
||||
});
|
||||
|
||||
it('sample app has correct values', async () => {
|
||||
const res = await sao.mockPrompt(template, {
|
||||
generateApp: true,
|
||||
generateApi: true,
|
||||
});
|
||||
|
||||
const contents = getFileContents(res.files['common/index.ts']);
|
||||
const controllerLine = contents.match("PLUGIN_ID = '(.*)'")[1];
|
||||
|
||||
expect(controllerLine).toContain('someFancyPlugin');
|
||||
});
|
||||
|
||||
it('includes dotfiles', async () => {
|
||||
const res = await sao.mockPrompt(template);
|
||||
expect(res.files['tsconfig.json']).toBeTruthy();
|
||||
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"');
|
||||
}
|
||||
});
|
||||
});
|
|
@ -1,9 +0,0 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
extends: ['@elastic/eslint-config-kibana', 'plugin:@elastic/eui/recommended'],
|
||||
<%_ if (!isKibanaPlugin) { -%>
|
||||
rules: {
|
||||
"@kbn/eslint/require-license-header": "off"
|
||||
}
|
||||
<%_ } -%>
|
||||
};
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"prefix": "<%= camelCase(name) %>",
|
||||
"paths": {
|
||||
"<%= camelCase(name) %>": "."
|
||||
},
|
||||
"translations": [
|
||||
"translations/ja-JP.json"
|
||||
]
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { AppMountParameters, CoreStart } from '<%= relRoot %>/src/core/public';
|
||||
import { AppPluginStartDependencies } from './types';
|
||||
import { <%= upperCamelCaseName %>App } from './components/app';
|
||||
|
||||
|
||||
export const renderApp = (
|
||||
{ notifications, http }: CoreStart,
|
||||
{ navigation }: AppPluginStartDependencies,
|
||||
{ appBasePath, element }: AppMountParameters
|
||||
) => {
|
||||
ReactDOM.render(
|
||||
<<%= upperCamelCaseName %>App
|
||||
basename={appBasePath}
|
||||
notifications={notifications}
|
||||
http={http}
|
||||
navigation={navigation}
|
||||
/>,
|
||||
element
|
||||
);
|
||||
|
||||
return () => ReactDOM.unmountComponentAtNode(element);
|
||||
};
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
<%_ if (hasScss) { -%>
|
||||
import './index.scss';
|
||||
<%_ } -%>
|
||||
|
||||
import { <%= upperCamelCaseName %>Plugin } from './plugin';
|
||||
|
||||
// This exports static code and TypeScript types,
|
||||
// as well as, Kibana Platform `plugin()` initializer.
|
||||
export function plugin() {
|
||||
return new <%= upperCamelCaseName %>Plugin();
|
||||
}
|
||||
export {
|
||||
<%= upperCamelCaseName %>PluginSetup,
|
||||
<%= upperCamelCaseName %>PluginStart,
|
||||
} from './types';
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
import { NavigationPublicPluginStart } from '<%= relRoot %>/src/plugins/navigation/public';
|
||||
|
||||
export interface <%= upperCamelCaseName %>PluginSetup {
|
||||
getGreeting: () => string;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface <%= upperCamelCaseName %>PluginStart {}
|
||||
|
||||
export interface AppPluginStartDependencies {
|
||||
navigation: NavigationPublicPluginStart
|
||||
};
|
|
@ -1,15 +0,0 @@
|
|||
import { PluginInitializerContext } from '<%= relRoot %>/src/core/server';
|
||||
import { <%= upperCamelCaseName %>Plugin } from './plugin';
|
||||
|
||||
|
||||
// This exports static code and TypeScript types,
|
||||
// as well as, Kibana Platform `plugin()` initializer.
|
||||
|
||||
export function plugin(initializerContext: PluginInitializerContext) {
|
||||
return new <%= upperCamelCaseName %>Plugin(initializerContext);
|
||||
}
|
||||
|
||||
export {
|
||||
<%= upperCamelCaseName %>PluginSetup,
|
||||
<%= upperCamelCaseName %>PluginStart,
|
||||
} from './types';
|
|
@ -1,30 +0,0 @@
|
|||
import { PluginInitializerContext, CoreSetup, CoreStart, Plugin, Logger } from '<%= relRoot %>/src/core/server';
|
||||
|
||||
import { <%= upperCamelCaseName %>PluginSetup, <%= upperCamelCaseName %>PluginStart } from './types';
|
||||
import { defineRoutes } from './routes';
|
||||
|
||||
export class <%= upperCamelCaseName %>Plugin
|
||||
implements Plugin<<%= upperCamelCaseName %>PluginSetup, <%= upperCamelCaseName %>PluginStart> {
|
||||
private readonly logger: Logger;
|
||||
|
||||
constructor(initializerContext: PluginInitializerContext) {
|
||||
this.logger = initializerContext.logger.get();
|
||||
}
|
||||
|
||||
public setup(core: CoreSetup) {
|
||||
this.logger.debug('<%= name %>: Setup');
|
||||
const router = core.http.createRouter();
|
||||
|
||||
// Register server side APIs
|
||||
defineRoutes(router);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
public start(core: CoreStart) {
|
||||
this.logger.debug('<%= name %>: Started');
|
||||
return {};
|
||||
}
|
||||
|
||||
public stop() {}
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
|
||||
{
|
||||
"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",
|
||||
}
|
||||
}
|
43
packages/kbn-plugin-generator/scripts/build.js
Normal file
43
packages/kbn-plugin-generator/scripts/build.js
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
const Path = require('path');
|
||||
|
||||
const { run } = require('@kbn/dev-utils');
|
||||
const del = require('del');
|
||||
const execa = require('execa');
|
||||
|
||||
run(
|
||||
async ({ flags }) => {
|
||||
await del(Path.resolve(__dirname, '../target'));
|
||||
|
||||
await execa(require.resolve('typescript/bin/tsc'), flags.watch ? ['--watch'] : [], {
|
||||
cwd: Path.resolve(__dirname, '..'),
|
||||
stdio: 'inherit',
|
||||
});
|
||||
},
|
||||
{
|
||||
flags: {
|
||||
boolean: ['watch'],
|
||||
help: `
|
||||
--watch Watch files and rebuild on changes
|
||||
`,
|
||||
},
|
||||
}
|
||||
);
|
103
packages/kbn-plugin-generator/src/ask_questions.ts
Normal file
103
packages/kbn-plugin-generator/src/ask_questions.ts
Normal file
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import Path from 'path';
|
||||
|
||||
import { REPO_ROOT } from '@kbn/dev-utils';
|
||||
import inquirer from 'inquirer';
|
||||
|
||||
export interface Answers {
|
||||
name: string;
|
||||
internal: boolean;
|
||||
internalLocation: string;
|
||||
ui: boolean;
|
||||
server: boolean;
|
||||
}
|
||||
|
||||
export const INTERNAL_PLUGIN_LOCATIONS: Array<{ name: string; value: string }> = [
|
||||
{
|
||||
name: 'Kibana Example',
|
||||
value: Path.resolve(REPO_ROOT, 'examples'),
|
||||
},
|
||||
{
|
||||
name: 'Kibana OSS',
|
||||
value: Path.resolve(REPO_ROOT, 'src/plugins'),
|
||||
},
|
||||
{
|
||||
name: 'Kibana OSS Functional Testing',
|
||||
value: Path.resolve(REPO_ROOT, 'test/plugin_functional/plugins'),
|
||||
},
|
||||
{
|
||||
name: 'X-Pack',
|
||||
value: Path.resolve(REPO_ROOT, 'x-pack/plugins'),
|
||||
},
|
||||
{
|
||||
name: 'X-Pack Functional Testing',
|
||||
value: Path.resolve(REPO_ROOT, 'x-pack/test/plugin_functional/plugins'),
|
||||
},
|
||||
];
|
||||
|
||||
export const QUESTIONS = [
|
||||
{
|
||||
name: 'name',
|
||||
message: 'Plugin name (use camelCase)',
|
||||
default: undefined,
|
||||
validate: (name: string) => (!name ? 'name is required' : true),
|
||||
},
|
||||
{
|
||||
name: 'internal',
|
||||
type: 'confirm',
|
||||
message: 'Will this plugin be part of the Kibana repository?',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
name: 'internalLocation',
|
||||
type: 'list',
|
||||
message: 'What type of internal plugin would you like to create',
|
||||
choices: INTERNAL_PLUGIN_LOCATIONS,
|
||||
default: INTERNAL_PLUGIN_LOCATIONS[0].value,
|
||||
when: ({ internal }: Answers) => internal,
|
||||
},
|
||||
{
|
||||
name: 'ui',
|
||||
type: 'confirm',
|
||||
message: 'Should an UI plugin be generated?',
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
name: 'server',
|
||||
type: 'confirm',
|
||||
message: 'Should a server plugin be generated?',
|
||||
default: true,
|
||||
},
|
||||
] as const;
|
||||
|
||||
export async function askQuestions(overrides: Partial<Answers>) {
|
||||
return await inquirer.prompt<Answers>(QUESTIONS, overrides);
|
||||
}
|
||||
|
||||
export function getDefaultAnswers(overrides: Partial<Answers>) {
|
||||
return QUESTIONS.reduce(
|
||||
(acc, q) => ({
|
||||
...acc,
|
||||
[q.name]: overrides[q.name] != null ? overrides[q.name] : q.default,
|
||||
}),
|
||||
{}
|
||||
) as Answers;
|
||||
}
|
68
packages/kbn-plugin-generator/src/casing.test.ts
Normal file
68
packages/kbn-plugin-generator/src/casing.test.ts
Normal file
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { camelCase, snakeCase, upperCamelCase } from './casing';
|
||||
|
||||
describe('camelCase', () => {
|
||||
it.each([
|
||||
['foo', 'foo'],
|
||||
['foo_bar', 'fooBar'],
|
||||
['foo bar', 'fooBar'],
|
||||
['fooBar', 'fooBar'],
|
||||
['___foo *$( bar 14', 'fooBar14'],
|
||||
['foo-bar', 'fooBar'],
|
||||
['FOO BAR', 'fooBar'],
|
||||
['FOO_BAR', 'fooBar'],
|
||||
['FOOBAR', 'foobar'],
|
||||
])('converts %j to %j', (input, output) => {
|
||||
expect(camelCase(input)).toBe(output);
|
||||
});
|
||||
});
|
||||
|
||||
describe('upperCamelCase', () => {
|
||||
it.each([
|
||||
['foo', 'Foo'],
|
||||
['foo_bar', 'FooBar'],
|
||||
['foo bar', 'FooBar'],
|
||||
['fooBar', 'FooBar'],
|
||||
['___foo *$( bar 14', 'FooBar14'],
|
||||
['foo-bar', 'FooBar'],
|
||||
['FOO BAR', 'FooBar'],
|
||||
['FOO_BAR', 'FooBar'],
|
||||
['FOOBAR', 'Foobar'],
|
||||
])('converts %j to %j', (input, output) => {
|
||||
expect(upperCamelCase(input)).toBe(output);
|
||||
});
|
||||
});
|
||||
|
||||
describe('snakeCase', () => {
|
||||
it.each([
|
||||
['foo', 'foo'],
|
||||
['foo_bar', 'foo_bar'],
|
||||
['foo bar', 'foo_bar'],
|
||||
['fooBar', 'foo_bar'],
|
||||
['___foo *$( bar 14', 'foo_bar_14'],
|
||||
['foo-bar', 'foo_bar'],
|
||||
['FOO BAR', 'foo_bar'],
|
||||
['FOO_BAR', 'foo_bar'],
|
||||
['FOOBAR', 'foobar'],
|
||||
])('converts %j to %j', (input, output) => {
|
||||
expect(snakeCase(input)).toBe(output);
|
||||
});
|
||||
});
|
32
packages/kbn-plugin-generator/src/casing.ts
Normal file
32
packages/kbn-plugin-generator/src/casing.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
const words = (input: string) =>
|
||||
input
|
||||
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
||||
.toLowerCase()
|
||||
.split(/[^a-z0-9]+/g)
|
||||
.filter(Boolean);
|
||||
|
||||
const upperFirst = (input: string) => `${input.slice(0, 1).toUpperCase()}${input.slice(1)}`;
|
||||
const lowerFirst = (input: string) => `${input.slice(0, 1).toLowerCase()}${input.slice(1)}`;
|
||||
|
||||
export const snakeCase = (input: string) => words(input).join('_');
|
||||
export const upperCamelCase = (input: string) => words(input).map(upperFirst).join('');
|
||||
export const camelCase = (input: string) => lowerFirst(upperCamelCase(input));
|
98
packages/kbn-plugin-generator/src/cli.ts
Normal file
98
packages/kbn-plugin-generator/src/cli.ts
Normal file
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import Path from 'path';
|
||||
import Fs from 'fs';
|
||||
|
||||
import execa from 'execa';
|
||||
import { REPO_ROOT, run, createFailError, createFlagError } from '@kbn/dev-utils';
|
||||
|
||||
import { snakeCase } from './casing';
|
||||
import { askQuestions, getDefaultAnswers } from './ask_questions';
|
||||
import { renderTemplates } from './render_template';
|
||||
|
||||
export function runCli() {
|
||||
run(
|
||||
async ({ log, flags }) => {
|
||||
const name = flags.name || undefined;
|
||||
if (name && typeof name !== 'string') {
|
||||
throw createFlagError(`expected one --name flag`);
|
||||
}
|
||||
|
||||
if (flags.yes && !name) {
|
||||
throw createFlagError(`passing --yes requires that you specify a name`);
|
||||
}
|
||||
|
||||
const overrides = {
|
||||
name,
|
||||
ui: typeof flags.ui === 'boolean' ? flags.ui : undefined,
|
||||
server: typeof flags.server === 'boolean' ? flags.server : undefined,
|
||||
};
|
||||
const answers = flags.yes ? getDefaultAnswers(overrides) : await askQuestions(overrides);
|
||||
|
||||
const outputDir = answers.internal
|
||||
? Path.resolve(answers.internalLocation, snakeCase(answers.name))
|
||||
: Path.resolve(REPO_ROOT, 'plugins', snakeCase(answers.name));
|
||||
|
||||
if (Fs.existsSync(outputDir)) {
|
||||
throw createFailError(`Target output directory [${outputDir}] already exists`);
|
||||
}
|
||||
|
||||
// process the template directory, creating the actual plugin files
|
||||
await renderTemplates({
|
||||
outputDir,
|
||||
answers,
|
||||
});
|
||||
|
||||
// init git repo in third party plugins
|
||||
if (!answers.internal) {
|
||||
await execa('git', ['init', outputDir]);
|
||||
}
|
||||
|
||||
log.success(
|
||||
`🎉\n\nYour plugin has been created in ${Path.relative(process.cwd(), outputDir)}\n`
|
||||
);
|
||||
},
|
||||
{
|
||||
usage: 'node scripts/generate_plugin',
|
||||
description: `
|
||||
Generate a fresh Kibana plugin in the plugins/ directory
|
||||
`,
|
||||
flags: {
|
||||
string: ['name'],
|
||||
boolean: ['yes', 'ui', 'server'],
|
||||
default: {
|
||||
ui: null,
|
||||
server: null,
|
||||
},
|
||||
alias: {
|
||||
y: 'yes',
|
||||
u: 'ui',
|
||||
s: 'server',
|
||||
},
|
||||
help: `
|
||||
--yes, -y Answer yes to all prompts, requires passing --name
|
||||
--name Set the plugin name
|
||||
--ui Generate a UI plugin
|
||||
--server Generate a Server plugin
|
||||
`,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
|
@ -16,9 +16,5 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
interface PluginGenerator {
|
||||
/**
|
||||
* Run plugin generator.
|
||||
*/
|
||||
run: (...args: any[]) => any;
|
||||
}
|
||||
|
||||
export * from './cli';
|
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import Path from 'path';
|
||||
|
||||
import del from 'del';
|
||||
import execa from 'execa';
|
||||
import { REPO_ROOT, createAbsolutePathSerializer } from '@kbn/dev-utils';
|
||||
import globby from 'globby';
|
||||
|
||||
const GENERATED_DIR = Path.resolve(REPO_ROOT, `plugins`);
|
||||
|
||||
expect.addSnapshotSerializer(createAbsolutePathSerializer());
|
||||
|
||||
beforeEach(async () => {
|
||||
await del(GENERATED_DIR, { force: true });
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await del(GENERATED_DIR, { force: true });
|
||||
});
|
||||
|
||||
it('generates a plugin', async () => {
|
||||
await execa(process.execPath, ['scripts/generate_plugin.js', '-y', '--name=foo'], {
|
||||
cwd: REPO_ROOT,
|
||||
buffer: true,
|
||||
});
|
||||
|
||||
const paths = await globby('**/*', {
|
||||
cwd: GENERATED_DIR,
|
||||
absolute: true,
|
||||
dot: true,
|
||||
onlyFiles: true,
|
||||
ignore: ['**/.git'],
|
||||
});
|
||||
|
||||
expect(paths.sort((a, b) => a.localeCompare(b))).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
<absolute path>/plugins/foo/.eslintrc.js,
|
||||
<absolute path>/plugins/foo/.gitignore,
|
||||
<absolute path>/plugins/foo/.i18nrc.json,
|
||||
<absolute path>/plugins/foo/common/index.ts,
|
||||
<absolute path>/plugins/foo/kibana.json,
|
||||
<absolute path>/plugins/foo/package.json,
|
||||
<absolute path>/plugins/foo/public/application.tsx,
|
||||
<absolute path>/plugins/foo/public/components/app.tsx,
|
||||
<absolute path>/plugins/foo/public/index.scss,
|
||||
<absolute path>/plugins/foo/public/index.ts,
|
||||
<absolute path>/plugins/foo/public/plugin.ts,
|
||||
<absolute path>/plugins/foo/public/types.ts,
|
||||
<absolute path>/plugins/foo/README.md,
|
||||
<absolute path>/plugins/foo/server/index.ts,
|
||||
<absolute path>/plugins/foo/server/plugin.ts,
|
||||
<absolute path>/plugins/foo/server/routes/index.ts,
|
||||
<absolute path>/plugins/foo/server/types.ts,
|
||||
<absolute path>/plugins/foo/translations/ja-JP.json,
|
||||
<absolute path>/plugins/foo/tsconfig.json,
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('generates a plugin without UI', async () => {
|
||||
await execa(process.execPath, ['scripts/generate_plugin.js', '--name=bar', '-y', '--no-ui'], {
|
||||
cwd: REPO_ROOT,
|
||||
buffer: true,
|
||||
});
|
||||
|
||||
const paths = await globby('**/*', {
|
||||
cwd: GENERATED_DIR,
|
||||
absolute: true,
|
||||
dot: true,
|
||||
onlyFiles: true,
|
||||
ignore: ['**/.git'],
|
||||
});
|
||||
|
||||
expect(paths.sort((a, b) => a.localeCompare(b))).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
<absolute path>/plugins/bar/.eslintrc.js,
|
||||
<absolute path>/plugins/bar/.gitignore,
|
||||
<absolute path>/plugins/bar/.i18nrc.json,
|
||||
<absolute path>/plugins/bar/common/index.ts,
|
||||
<absolute path>/plugins/bar/kibana.json,
|
||||
<absolute path>/plugins/bar/package.json,
|
||||
<absolute path>/plugins/bar/README.md,
|
||||
<absolute path>/plugins/bar/server/index.ts,
|
||||
<absolute path>/plugins/bar/server/plugin.ts,
|
||||
<absolute path>/plugins/bar/server/routes/index.ts,
|
||||
<absolute path>/plugins/bar/server/types.ts,
|
||||
<absolute path>/plugins/bar/tsconfig.json,
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('generates a plugin without server plugin', async () => {
|
||||
await execa(process.execPath, ['scripts/generate_plugin.js', '--name=baz', '-y', '--no-server'], {
|
||||
cwd: REPO_ROOT,
|
||||
buffer: true,
|
||||
});
|
||||
|
||||
const paths = await globby('**/*', {
|
||||
cwd: GENERATED_DIR,
|
||||
absolute: true,
|
||||
dot: true,
|
||||
onlyFiles: true,
|
||||
ignore: ['**/.git'],
|
||||
});
|
||||
|
||||
expect(paths.sort((a, b) => a.localeCompare(b))).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
<absolute path>/plugins/baz/.eslintrc.js,
|
||||
<absolute path>/plugins/baz/.gitignore,
|
||||
<absolute path>/plugins/baz/.i18nrc.json,
|
||||
<absolute path>/plugins/baz/common/index.ts,
|
||||
<absolute path>/plugins/baz/kibana.json,
|
||||
<absolute path>/plugins/baz/package.json,
|
||||
<absolute path>/plugins/baz/public/application.tsx,
|
||||
<absolute path>/plugins/baz/public/components/app.tsx,
|
||||
<absolute path>/plugins/baz/public/index.scss,
|
||||
<absolute path>/plugins/baz/public/index.ts,
|
||||
<absolute path>/plugins/baz/public/plugin.ts,
|
||||
<absolute path>/plugins/baz/public/types.ts,
|
||||
<absolute path>/plugins/baz/README.md,
|
||||
<absolute path>/plugins/baz/translations/ja-JP.json,
|
||||
<absolute path>/plugins/baz/tsconfig.json,
|
||||
]
|
||||
`);
|
||||
});
|
127
packages/kbn-plugin-generator/src/render_template.ts
Normal file
127
packages/kbn-plugin-generator/src/render_template.ts
Normal file
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import Path from 'path';
|
||||
import { pipeline } from 'stream';
|
||||
import { promisify } from 'util';
|
||||
|
||||
import vfs from 'vinyl-fs';
|
||||
import prettier from 'prettier';
|
||||
import { REPO_ROOT } from '@kbn/dev-utils';
|
||||
import ejs from 'ejs';
|
||||
|
||||
import { snakeCase, camelCase, upperCamelCase } from './casing';
|
||||
import { excludeFiles, tapFileStream } from './streams';
|
||||
import { Answers } from './ask_questions';
|
||||
|
||||
const asyncPipeline = promisify(pipeline);
|
||||
|
||||
/**
|
||||
* Stream all the files from the template directory, ignoring
|
||||
* certain files based on the answers, process the .ejs templates
|
||||
* to the output files they represent, renaming the .ejs files to
|
||||
* remove that extension, then run every file through prettier
|
||||
* before writing the files to the output directory.
|
||||
*/
|
||||
export async function renderTemplates({
|
||||
outputDir,
|
||||
answers,
|
||||
}: {
|
||||
outputDir: string;
|
||||
answers: Answers;
|
||||
}) {
|
||||
const prettierConfig = await prettier.resolveConfig(process.cwd());
|
||||
|
||||
const defaultTemplateData = {
|
||||
name: answers.name,
|
||||
|
||||
internalPlugin: !!answers.internal,
|
||||
thirdPartyPlugin: !answers.internal,
|
||||
|
||||
hasServer: !!answers.server,
|
||||
hasUi: !!answers.ui,
|
||||
|
||||
camelCase,
|
||||
snakeCase,
|
||||
upperCamelCase,
|
||||
};
|
||||
|
||||
await asyncPipeline(
|
||||
vfs.src(['**/*'], {
|
||||
dot: true,
|
||||
buffer: true,
|
||||
nodir: true,
|
||||
cwd: Path.resolve(__dirname, '../template'),
|
||||
}),
|
||||
|
||||
// exclude files from the template based on selected options, patterns
|
||||
// are matched without the .ejs extension
|
||||
excludeFiles(
|
||||
([] as string[]).concat(
|
||||
answers.ui ? [] : 'public/**/*',
|
||||
answers.ui && !answers.internal ? [] : ['translations/**/*', 'i18nrc.json'],
|
||||
answers.server ? [] : 'server/**/*',
|
||||
!answers.internal ? [] : ['eslintrc.js', 'tsconfig.json', 'package.json', '.gitignore']
|
||||
)
|
||||
),
|
||||
|
||||
// render .ejs templates and rename to not use .ejs extension
|
||||
tapFileStream((file) => {
|
||||
if (file.extname !== '.ejs') {
|
||||
return;
|
||||
}
|
||||
|
||||
const templateData = {
|
||||
...defaultTemplateData,
|
||||
importFromRoot(rootRelative: string) {
|
||||
const filesOutputDirname = Path.dirname(Path.resolve(outputDir, file.relative));
|
||||
const target = Path.resolve(REPO_ROOT, rootRelative);
|
||||
return Path.relative(filesOutputDirname, target);
|
||||
},
|
||||
};
|
||||
|
||||
// render source and write back to file object
|
||||
file.contents = Buffer.from(
|
||||
ejs.render(file.contents.toString('utf8'), templateData, {
|
||||
beautify: false,
|
||||
})
|
||||
);
|
||||
|
||||
// file.stem is the basename but without the extension
|
||||
file.basename = file.stem;
|
||||
}),
|
||||
|
||||
// format each file with prettier
|
||||
tapFileStream((file) => {
|
||||
if (!file.extname) {
|
||||
return;
|
||||
}
|
||||
|
||||
file.contents = Buffer.from(
|
||||
prettier.format(file.contents.toString('utf8'), {
|
||||
...prettierConfig,
|
||||
filepath: file.path,
|
||||
})
|
||||
);
|
||||
}),
|
||||
|
||||
// write files to disk
|
||||
vfs.dest(outputDir)
|
||||
);
|
||||
}
|
73
packages/kbn-plugin-generator/src/streams.ts
Normal file
73
packages/kbn-plugin-generator/src/streams.ts
Normal file
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { Transform } from 'stream';
|
||||
|
||||
import File from 'vinyl';
|
||||
import { Minimatch } from 'minimatch';
|
||||
|
||||
interface BufferedFile extends File {
|
||||
contents: Buffer;
|
||||
isDirectory(): false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a transform stream that processes Vinyl fs streams and
|
||||
* calls a function for each file, allowing the function to either
|
||||
* mutate the file, replace it with another file (return a new File
|
||||
* object), or drop it from the stream (return null)
|
||||
*/
|
||||
export const tapFileStream = (
|
||||
fn: (file: BufferedFile) => File | void | null | Promise<File | void | null>
|
||||
) =>
|
||||
new Transform({
|
||||
objectMode: true,
|
||||
transform(file: BufferedFile, _, cb) {
|
||||
Promise.resolve(file)
|
||||
.then(fn)
|
||||
.then(
|
||||
(result) => {
|
||||
// drop the file when null is returned
|
||||
if (result === null) {
|
||||
cb();
|
||||
} else {
|
||||
cb(undefined, result || file);
|
||||
}
|
||||
},
|
||||
(error) => cb(error)
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export const excludeFiles = (globs: string[]) => {
|
||||
const patterns = globs.map(
|
||||
(g) =>
|
||||
new Minimatch(g, {
|
||||
matchBase: true,
|
||||
})
|
||||
);
|
||||
|
||||
return tapFileStream((file) => {
|
||||
const path = file.relative.replace(/\.ejs$/, '');
|
||||
const exclude = patterns.some((p) => p.match(path));
|
||||
if (exclude) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
};
|
10
packages/kbn-plugin-generator/template/.eslintrc.js.ejs
Normal file
10
packages/kbn-plugin-generator/template/.eslintrc.js.ejs
Normal file
|
@ -0,0 +1,10 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
extends: [
|
||||
'@elastic/eslint-config-kibana',
|
||||
'plugin:@elastic/eui/recommended'
|
||||
],
|
||||
rules: {
|
||||
'@kbn/eslint/require-license-header': 'off',
|
||||
},
|
||||
};
|
2
packages/kbn-plugin-generator/template/.gitignore
vendored
Normal file
2
packages/kbn-plugin-generator/template/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/build
|
||||
/target
|
9
packages/kbn-plugin-generator/template/.i18nrc.json.ejs
Normal file
9
packages/kbn-plugin-generator/template/.i18nrc.json.ejs
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"prefix": "<%= camelCase(name) %>",
|
||||
"paths": {
|
||||
"<%= camelCase(name) %>": "."
|
||||
},
|
||||
"translations": [
|
||||
"translations/ja-JP.json"
|
||||
]
|
||||
}
|
|
@ -1,8 +1,6 @@
|
|||
# <%= name %>
|
||||
|
||||
<%- (description || '').split('\n').map(function (line) {
|
||||
return '> ' + line
|
||||
}).join('\n') %>
|
||||
A Kibana plugin
|
||||
|
||||
---
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"id": "<%= camelCase(name) %>",
|
||||
"version": "<%= kbnVersion %>",
|
||||
"version": "1.0.0",
|
||||
"kibanaVersion": "kibana",
|
||||
"server": <%= hasServer %>,
|
||||
"ui": <%= hasUi %>,
|
||||
"requiredPlugins": ["navigation"],
|
8
packages/kbn-plugin-generator/template/package.json.ejs
Normal file
8
packages/kbn-plugin-generator/template/package.json.ejs
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "<%= camelCase(name) %>",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"kbn": "node ../../scripts/kbn"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { AppMountParameters, CoreStart } from '<%= importFromRoot('src/core/public') %>';
|
||||
import { AppPluginStartDependencies } from './types';
|
||||
import { <%= upperCamelCase(name) %>App } from './components/app';
|
||||
|
||||
|
||||
export const renderApp = (
|
||||
{ notifications, http }: CoreStart,
|
||||
{ navigation }: AppPluginStartDependencies,
|
||||
{ appBasePath, element }: AppMountParameters
|
||||
) => {
|
||||
ReactDOM.render(
|
||||
<<%= upperCamelCase(name) %>App
|
||||
basename={appBasePath}
|
||||
notifications={notifications}
|
||||
http={http}
|
||||
navigation={navigation}
|
||||
/>,
|
||||
element
|
||||
);
|
||||
|
||||
return () => ReactDOM.unmountComponentAtNode(element);
|
||||
};
|
|
@ -1,22 +1,3 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage, I18nProvider } from '@kbn/i18n/react';
|
||||
|
@ -35,36 +16,36 @@ import {
|
|||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { CoreStart } from '<%= relRoot %>/../src/core/public';
|
||||
import { NavigationPublicPluginStart } from '<%= relRoot %>/../src/plugins/navigation/public';
|
||||
import { CoreStart } from '<%= importFromRoot('src/core/public') %>';
|
||||
import { NavigationPublicPluginStart } from '<%= importFromRoot('src/plugins/navigation/public') %>';
|
||||
|
||||
import { PLUGIN_ID, PLUGIN_NAME } from '../../common';
|
||||
|
||||
interface <%= upperCamelCaseName %>AppDeps {
|
||||
interface <%= upperCamelCase(name) %>AppDeps {
|
||||
basename: string;
|
||||
notifications: CoreStart['notifications'];
|
||||
http: CoreStart['http'];
|
||||
navigation: NavigationPublicPluginStart;
|
||||
}
|
||||
|
||||
export const <%= upperCamelCaseName %>App = ({ basename, notifications, http, navigation }: <%= upperCamelCaseName %>AppDeps) => {
|
||||
export const <%= upperCamelCase(name) %>App = ({ basename, notifications, http, navigation }: <%= upperCamelCase(name) %>AppDeps) => {
|
||||
// Use React hooks to manage state.
|
||||
const [timestamp, setTimestamp] = useState<string | undefined>();
|
||||
|
||||
const onClickHandler = () => {
|
||||
<%_ if (generateApi) { -%>
|
||||
// Use the core http service to make a response to the server API.
|
||||
http.get('/api/<%= snakeCase(name) %>/example').then(res => {
|
||||
setTimestamp(res.time);
|
||||
// Use the core notifications service to display a success message.
|
||||
notifications.toasts.addSuccess(i18n.translate('<%= camelCase(name) %>.dataUpdated', {
|
||||
defaultMessage: 'Data updated',
|
||||
}));
|
||||
});
|
||||
<%_ } else { -%>
|
||||
setTimestamp(new Date().toISOString());
|
||||
notifications.toasts.addSuccess(PLUGIN_NAME);
|
||||
<%_ } -%>
|
||||
<% if (hasServer) { %>
|
||||
// Use the core http service to make a response to the server API.
|
||||
http.get('/api/<%= snakeCase(name) %>/example').then(res => {
|
||||
setTimestamp(res.time);
|
||||
// Use the core notifications service to display a success message.
|
||||
notifications.toasts.addSuccess(i18n.translate('<%= camelCase(name) %>.dataUpdated', {
|
||||
defaultMessage: 'Data updated',
|
||||
}));
|
||||
});
|
||||
<% } else { %>
|
||||
setTimestamp(new Date().toISOString());
|
||||
notifications.toasts.addSuccess(PLUGIN_NAME);
|
||||
<% } %>
|
||||
};
|
||||
|
||||
// Render the application DOM.
|
||||
|
@ -115,7 +96,10 @@ export const <%= upperCamelCaseName %>App = ({ basename, notifications, http, na
|
|||
/>
|
||||
</p>
|
||||
<EuiButton type="primary" size="s" onClick={onClickHandler}>
|
||||
<FormattedMessage id="<%= camelCase(name) %>.buttonText" defaultMessage="<%_ if (generateApi) { -%>Get data<%_ } else { -%>Click me<%_ } -%>" />
|
||||
<FormattedMessage
|
||||
id="<%= camelCase(name) %>.buttonText"
|
||||
defaultMessage="<%= hasServer ? 'Get data' : 'Click me' %>"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiText>
|
||||
</EuiPageContentBody>
|
14
packages/kbn-plugin-generator/template/public/index.ts.ejs
Normal file
14
packages/kbn-plugin-generator/template/public/index.ts.ejs
Normal file
|
@ -0,0 +1,14 @@
|
|||
import './index.scss';
|
||||
|
||||
import { <%= upperCamelCase(name) %>Plugin } from './plugin';
|
||||
|
||||
// This exports static code and TypeScript types,
|
||||
// as well as, Kibana Platform `plugin()` initializer.
|
||||
export function plugin() {
|
||||
return new <%= upperCamelCase(name) %>Plugin();
|
||||
}
|
||||
export {
|
||||
<%= upperCamelCase(name) %>PluginSetup,
|
||||
<%= upperCamelCase(name) %>PluginStart,
|
||||
} from './types';
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { AppMountParameters, CoreSetup, CoreStart, Plugin } from '<%= relRoot %>/src/core/public';
|
||||
import { <%= upperCamelCaseName %>PluginSetup, <%= upperCamelCaseName %>PluginStart, AppPluginStartDependencies } from './types';
|
||||
import { AppMountParameters, CoreSetup, CoreStart, Plugin } from '<%= importFromRoot('src/core/public') %>';
|
||||
import { <%= upperCamelCase(name) %>PluginSetup, <%= upperCamelCase(name) %>PluginStart, AppPluginStartDependencies } from './types';
|
||||
import { PLUGIN_NAME } from '../common';
|
||||
|
||||
export class <%= upperCamelCaseName %>Plugin
|
||||
implements Plugin<<%= upperCamelCaseName %>PluginSetup, <%= upperCamelCaseName %>PluginStart> {
|
||||
|
||||
public setup(core: CoreSetup): <%= upperCamelCaseName %>PluginSetup {
|
||||
export class <%= upperCamelCase(name) %>Plugin
|
||||
implements Plugin<<%= upperCamelCase(name) %>PluginSetup, <%= upperCamelCase(name) %>PluginStart> {
|
||||
|
||||
public setup(core: CoreSetup): <%= upperCamelCase(name) %>PluginSetup {
|
||||
// Register an application into the side navigation menu
|
||||
core.application.register({
|
||||
id: '<%= camelCase(name) %>',
|
||||
|
@ -34,7 +34,7 @@ export class <%= upperCamelCaseName %>Plugin
|
|||
};
|
||||
}
|
||||
|
||||
public start(core: CoreStart): <%= upperCamelCaseName %>PluginStart {
|
||||
public start(core: CoreStart): <%= upperCamelCase(name) %>PluginStart {
|
||||
return {};
|
||||
}
|
||||
|
11
packages/kbn-plugin-generator/template/public/types.ts.ejs
Normal file
11
packages/kbn-plugin-generator/template/public/types.ts.ejs
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { NavigationPublicPluginStart } from '<%= importFromRoot('src/plugins/navigation/public') %>';
|
||||
|
||||
export interface <%= upperCamelCase(name) %>PluginSetup {
|
||||
getGreeting: () => string;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface <%= upperCamelCase(name) %>PluginStart {}
|
||||
|
||||
export interface AppPluginStartDependencies {
|
||||
navigation: NavigationPublicPluginStart
|
||||
};
|
15
packages/kbn-plugin-generator/template/server/index.ts.ejs
Normal file
15
packages/kbn-plugin-generator/template/server/index.ts.ejs
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { PluginInitializerContext } from '<%= importFromRoot('src/core/server') %>';
|
||||
import { <%= upperCamelCase(name) %>Plugin } from './plugin';
|
||||
|
||||
|
||||
// This exports static code and TypeScript types,
|
||||
// as well as, Kibana Platform `plugin()` initializer.
|
||||
|
||||
export function plugin(initializerContext: PluginInitializerContext) {
|
||||
return new <%= upperCamelCase(name) %>Plugin(initializerContext);
|
||||
}
|
||||
|
||||
export {
|
||||
<%= upperCamelCase(name) %>PluginSetup,
|
||||
<%= upperCamelCase(name) %>PluginStart,
|
||||
} from './types';
|
40
packages/kbn-plugin-generator/template/server/plugin.ts.ejs
Normal file
40
packages/kbn-plugin-generator/template/server/plugin.ts.ejs
Normal file
|
@ -0,0 +1,40 @@
|
|||
import {
|
||||
PluginInitializerContext,
|
||||
CoreSetup,
|
||||
CoreStart,
|
||||
Plugin,
|
||||
Logger
|
||||
} from '<%= importFromRoot('src/core/server') %>';
|
||||
|
||||
import {
|
||||
<%= upperCamelCase(name) %>PluginSetup,
|
||||
<%= upperCamelCase(name) %>PluginStart
|
||||
} from './types';
|
||||
import { defineRoutes } from './routes';
|
||||
|
||||
export class <%= upperCamelCase(name) %>Plugin
|
||||
implements Plugin<<%= upperCamelCase(name) %>PluginSetup, <%= upperCamelCase(name) %>PluginStart> {
|
||||
|
||||
private readonly logger: Logger;
|
||||
|
||||
constructor(initializerContext: PluginInitializerContext) {
|
||||
this.logger = initializerContext.logger.get();
|
||||
}
|
||||
|
||||
public setup(core: CoreSetup) {
|
||||
this.logger.debug('<%= name %>: Setup');
|
||||
const router = core.http.createRouter();
|
||||
|
||||
// Register server side APIs
|
||||
defineRoutes(router);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
public start(core: CoreStart) {
|
||||
this.logger.debug('<%= name %>: Started');
|
||||
return {};
|
||||
}
|
||||
|
||||
public stop() {}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
import { IRouter } from '<%= relRoot %>/../src/core/server';
|
||||
import { IRouter } from '<%= importFromRoot('src/core/server') %>';
|
||||
|
||||
|
||||
export function defineRoutes(router: IRouter) {
|
||||
router.get(
|
|
@ -1,4 +1,4 @@
|
|||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface <%= upperCamelCaseName %>PluginSetup {}
|
||||
export interface <%= upperCamelCase(name) %>PluginSetup {}
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface <%= upperCamelCaseName %>PluginStart {}
|
||||
export interface <%= upperCamelCase(name) %>PluginStart {}
|
|
@ -0,0 +1,81 @@
|
|||
{
|
||||
"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",
|
||||
}
|
||||
}
|
|
@ -1,5 +1,12 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": ["**/*", "index.js.d.ts"],
|
||||
"exclude": ["sao_template/template/*"]
|
||||
"compilerOptions": {
|
||||
"outDir": "target",
|
||||
"target": "ES2019",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["src/template/*"]
|
||||
}
|
||||
|
|
6255
packages/kbn-pm/dist/index.js
vendored
6255
packages/kbn-pm/dist/index.js
vendored
File diff suppressed because one or more lines are too long
|
@ -17,5 +17,5 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
require('../src/setup_node_env');
|
||||
require('@kbn/plugin-generator').run(process.argv.slice(2));
|
||||
require('../src/setup_node_env/prebuilt_dev_only_entry');
|
||||
require('@kbn/plugin-generator').runCli();
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { dirname, extname, join, relative, resolve, sep } from 'path';
|
||||
import { dirname, extname, join, relative, resolve, sep, basename } from 'path';
|
||||
|
||||
export class File {
|
||||
private path: string;
|
||||
|
@ -38,6 +38,12 @@ export class File {
|
|||
return this.relativePath;
|
||||
}
|
||||
|
||||
public getWithoutExtension() {
|
||||
const directory = dirname(this.path);
|
||||
const stem = basename(this.path, this.ext);
|
||||
return new File(resolve(directory, stem));
|
||||
}
|
||||
|
||||
public isJs() {
|
||||
return this.ext === '.js';
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ export async function extractUntrackedMessagesTask({
|
|||
'**/build/**',
|
||||
'**/__fixtures__/**',
|
||||
'**/packages/kbn-i18n/**',
|
||||
'**/packages/kbn-plugin-generator/sao_template/**',
|
||||
'**/packages/kbn-plugin-generator/template/**',
|
||||
'**/packages/kbn-ui-framework/generator-kui/**',
|
||||
'**/target/**',
|
||||
'**/test/**',
|
||||
|
|
|
@ -109,6 +109,14 @@ export const IGNORE_DIRECTORY_GLOBS = [
|
|||
'packages/kbn-optimizer/src/__fixtures__/mock_repo/x-pack',
|
||||
];
|
||||
|
||||
/**
|
||||
* These patterns identify files which should have the extension stripped
|
||||
* to reveal the actual name that should be checked.
|
||||
*
|
||||
* @type {Array}
|
||||
*/
|
||||
export const REMOVE_EXTENSION = ['packages/kbn-plugin-generator/template/**/*.ejs'];
|
||||
|
||||
/**
|
||||
* DO NOT ADD FILES TO THIS LIST!!
|
||||
*
|
||||
|
|
|
@ -29,6 +29,7 @@ import {
|
|||
IGNORE_FILE_GLOBS,
|
||||
TEMPORARILY_IGNORED_PATHS,
|
||||
KEBAB_CASE_DIRECTORY_GLOBS,
|
||||
REMOVE_EXTENSION,
|
||||
} from './casing_check_config';
|
||||
|
||||
const NON_SNAKE_CASE_RE = /[A-Z \-]/;
|
||||
|
@ -143,6 +144,10 @@ async function checkForSnakeCase(log, files) {
|
|||
}
|
||||
|
||||
export async function checkFileCasing(log, files) {
|
||||
files = files.map((f) =>
|
||||
matchesAnyGlob(f.getRelativePath(), REMOVE_EXTENSION) ? f.getWithoutExtension() : f
|
||||
);
|
||||
|
||||
await checkForKebabCase(log, files);
|
||||
await checkForSnakeCase(log, files);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue