Skip to content

Commit 64311d3

Browse files
Spencertylersmalleyspalger
authored
[plugin-helpers] improve 3rd party KP plugin support (#75019)
Co-authored-by: Tyler Smalley <[email protected]> Co-authored-by: spalger <[email protected]>
1 parent 89ae032 commit 64311d3

File tree

79 files changed

+1146
-1681
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+1146
-1681
lines changed

docs/developer/plugin/external-plugin-functional-tests.asciidoc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ To get started copy and paste this example to `test/functional/config.js`:
1313
["source","js"]
1414
-----------
1515
import { resolve } from 'path';
16-
import { resolveKibanaPath } from '@kbn/plugin-helpers';
16+
import { REPO_ROOT } from '@kbn/dev-utils';
1717
1818
import { MyServiceProvider } from './services/my_service';
1919
import { MyAppPageProvider } from './services/my_app_page';
@@ -24,7 +24,7 @@ export default async function ({ readConfigFile }) {
2424
2525
// read the {kib} config file so that we can utilize some of
2626
// its services and PageObjects
27-
const kibanaConfig = await readConfigFile(resolveKibanaPath('test/functional/config.js'));
27+
const kibanaConfig = await readConfigFile(resolve(REPO_ROOT, 'test/functional/config.js'));
2828
2929
return {
3030
// list paths to the files that contain your plugins tests
Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,38 @@
11
{
22
"name": "@kbn/dev-utils",
3-
"main": "./target/index.js",
43
"version": "1.0.0",
5-
"license": "Apache-2.0",
64
"private": true,
5+
"license": "Apache-2.0",
6+
"main": "./target/index.js",
77
"scripts": {
88
"build": "tsc",
99
"kbn:bootstrap": "yarn build",
1010
"kbn:watch": "yarn build --watch"
1111
},
1212
"dependencies": {
13+
"@babel/core": "^7.11.1",
1314
"axios": "^0.19.0",
1415
"chalk": "^4.1.0",
16+
"cheerio": "0.22.0",
1517
"dedent": "^0.7.0",
1618
"execa": "^4.0.2",
1719
"exit-hook": "^2.2.0",
1820
"getopts": "^2.2.5",
21+
"globby": "^8.0.1",
1922
"load-json-file": "^6.2.0",
20-
"normalize-path": "^3.0.0",
23+
"markdown-it": "^10.0.0",
2124
"moment": "^2.24.0",
25+
"normalize-path": "^3.0.0",
2226
"rxjs": "^6.5.5",
2327
"strip-ansi": "^6.0.0",
2428
"tree-kill": "^1.2.2",
25-
"tslib": "^2.0.0"
29+
"vinyl": "^2.2.0"
2630
},
2731
"devDependencies": {
28-
"typescript": "4.0.2",
32+
"@kbn/babel-preset": "1.0.0",
2933
"@kbn/expect": "1.0.0",
30-
"chance": "1.0.18"
34+
"@types/vinyl": "^2.0.4",
35+
"chance": "1.0.18",
36+
"typescript": "4.0.2"
3137
}
3238
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
import File from 'vinyl';
21+
import * as Babel from '@babel/core';
22+
23+
const transformedFiles = new WeakSet<File>();
24+
25+
/**
26+
* Returns a promise that resolves when the file has been
27+
* mutated so the contents of the file are tranformed with
28+
* babel, include inline sourcemaps, and the filename has
29+
* been updated to use .js.
30+
*
31+
* If the file was previously transformed with this function
32+
* the promise will just resolve immediately.
33+
*/
34+
export async function transformFileWithBabel(file: File) {
35+
if (!(file.contents instanceof Buffer)) {
36+
throw new Error('file must be buffered');
37+
}
38+
39+
if (transformedFiles.has(file)) {
40+
return;
41+
}
42+
43+
const source = file.contents.toString('utf8');
44+
const result = await Babel.transformAsync(source, {
45+
babelrc: false,
46+
configFile: false,
47+
sourceMaps: 'inline',
48+
filename: file.path,
49+
presets: [require.resolve('@kbn/babel-preset/node_preset')],
50+
});
51+
52+
if (!result || typeof result.code !== 'string') {
53+
throw new Error('babel transformation failed without an error...');
54+
}
55+
56+
file.contents = Buffer.from(result.code);
57+
file.extname = '.js';
58+
transformedFiles.add(file);
59+
}

packages/kbn-dev-utils/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,6 @@ export * from './stdio';
4141
export * from './ci_stats_reporter';
4242
export * from './plugin_list';
4343
export * from './simple_kibana_platform_plugin_discovery';
44+
export * from './streams';
45+
export * from './babel';
46+
export * from './parse_kibana_platform_plugin';
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
import Path from 'path';
21+
import loadJsonFile from 'load-json-file';
22+
23+
export interface KibanaPlatformPlugin {
24+
readonly directory: string;
25+
readonly manifestPath: string;
26+
readonly manifest: {
27+
id: string;
28+
ui: boolean;
29+
server: boolean;
30+
[key: string]: unknown;
31+
};
32+
}
33+
34+
export function parseKibanaPlatformPlugin(manifestPath: string): KibanaPlatformPlugin {
35+
if (!Path.isAbsolute(manifestPath)) {
36+
throw new TypeError('expected new platform manifest path to be absolute');
37+
}
38+
39+
const manifest = loadJsonFile.sync(manifestPath);
40+
if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest)) {
41+
throw new TypeError('expected new platform plugin manifest to be a JSON encoded object');
42+
}
43+
44+
if (typeof manifest.id !== 'string') {
45+
throw new TypeError('expected new platform plugin manifest to have a string id');
46+
}
47+
48+
return {
49+
directory: Path.dirname(manifestPath),
50+
manifestPath,
51+
manifest: {
52+
...manifest,
53+
54+
ui: !!manifest.ui,
55+
server: !!manifest.server,
56+
id: manifest.id,
57+
},
58+
};
59+
}

packages/kbn-dev-utils/src/run/flags.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ export function mergeFlagOptions(global: FlagOptions = {}, local: FlagOptions =
5252
boolean: [...(global.boolean || []), ...(local.boolean || [])],
5353
string: [...(global.string || []), ...(local.string || [])],
5454
default: {
55-
...global.alias,
56-
...local.alias,
55+
...global.default,
56+
...local.default,
5757
},
5858

5959
help: local.help,

packages/kbn-dev-utils/src/serializers/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ export * from './absolute_path_serializer';
2121
export * from './strip_ansi_serializer';
2222
export * from './recursive_serializer';
2323
export * from './any_instance_serizlizer';
24+
export * from './replace_serializer';
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
import { createRecursiveSerializer } from './recursive_serializer';
21+
22+
type Replacer = (substring: string, ...args: any[]) => string;
23+
24+
export function createReplaceSerializer(
25+
toReplace: string | RegExp,
26+
replaceWith: string | Replacer
27+
) {
28+
return createRecursiveSerializer(
29+
typeof toReplace === 'string'
30+
? (v: any) => typeof v === 'string' && v.includes(toReplace)
31+
: (v: any) => typeof v === 'string' && toReplace.test(v),
32+
typeof replaceWith === 'string'
33+
? (v: string) => v.replace(toReplace, replaceWith)
34+
: (v: string) => v.replace(toReplace, replaceWith)
35+
);
36+
}

packages/kbn-dev-utils/src/simple_kibana_platform_plugin_discovery.ts

Lines changed: 12 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -20,67 +20,37 @@
2020
import Path from 'path';
2121

2222
import globby from 'globby';
23-
import loadJsonFile from 'load-json-file';
2423

25-
export interface KibanaPlatformPlugin {
26-
readonly directory: string;
27-
readonly manifestPath: string;
28-
readonly manifest: {
29-
id: string;
30-
[key: string]: unknown;
31-
};
32-
}
24+
import { parseKibanaPlatformPlugin } from './parse_kibana_platform_plugin';
3325

3426
/**
3527
* Helper to find the new platform plugins.
3628
*/
37-
export function simpleKibanaPlatformPluginDiscovery(scanDirs: string[], paths: string[]) {
29+
export function simpleKibanaPlatformPluginDiscovery(scanDirs: string[], pluginPaths: string[]) {
3830
const patterns = Array.from(
3931
new Set([
4032
// find kibana.json files up to 5 levels within the scan dir
4133
...scanDirs.reduce(
4234
(acc: string[], dir) => [
4335
...acc,
44-
`${dir}/*/kibana.json`,
45-
`${dir}/*/*/kibana.json`,
46-
`${dir}/*/*/*/kibana.json`,
47-
`${dir}/*/*/*/*/kibana.json`,
48-
`${dir}/*/*/*/*/*/kibana.json`,
36+
Path.resolve(dir, '*/kibana.json'),
37+
Path.resolve(dir, '*/*/kibana.json'),
38+
Path.resolve(dir, '*/*/*/kibana.json'),
39+
Path.resolve(dir, '*/*/*/*/kibana.json'),
40+
Path.resolve(dir, '*/*/*/*/*/kibana.json'),
4941
],
5042
[]
5143
),
52-
...paths.map((path) => `${path}/kibana.json`),
44+
...pluginPaths.map((path) => Path.resolve(path, `kibana.json`)),
5345
])
5446
);
5547

5648
const manifestPaths = globby.sync(patterns, { absolute: true }).map((path) =>
57-
// absolute paths returned from globby are using normalize or something so the path separators are `/` even on windows, Path.resolve solves this
49+
// absolute paths returned from globby are using normalize or
50+
// something so the path separators are `/` even on windows,
51+
// Path.resolve solves this
5852
Path.resolve(path)
5953
);
6054

61-
return manifestPaths.map(
62-
(manifestPath): KibanaPlatformPlugin => {
63-
if (!Path.isAbsolute(manifestPath)) {
64-
throw new TypeError('expected new platform manifest path to be absolute');
65-
}
66-
67-
const manifest = loadJsonFile.sync(manifestPath);
68-
if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest)) {
69-
throw new TypeError('expected new platform plugin manifest to be a JSON encoded object');
70-
}
71-
72-
if (typeof manifest.id !== 'string') {
73-
throw new TypeError('expected new platform plugin manifest to have a string id');
74-
}
75-
76-
return {
77-
directory: Path.dirname(manifestPath),
78-
manifestPath,
79-
manifest: {
80-
...manifest,
81-
id: manifest.id,
82-
},
83-
};
84-
}
85-
);
55+
return manifestPaths.map(parseKibanaPlatformPlugin);
8656
}

packages/kbn-plugin-generator/src/streams.ts renamed to packages/kbn-dev-utils/src/streams.ts

Lines changed: 21 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import { Transform } from 'stream';
2121

2222
import File from 'vinyl';
23-
import { Minimatch } from 'minimatch';
2423

2524
interface BufferedFile extends File {
2625
contents: Buffer;
@@ -33,41 +32,31 @@ interface BufferedFile extends File {
3332
* mutate the file, replace it with another file (return a new File
3433
* object), or drop it from the stream (return null)
3534
*/
36-
export const tapFileStream = (
35+
export const transformFileStream = (
3736
fn: (file: BufferedFile) => File | void | null | Promise<File | void | null>
3837
) =>
3938
new Transform({
4039
objectMode: true,
41-
transform(file: BufferedFile, _, cb) {
42-
Promise.resolve(file)
43-
.then(fn)
44-
.then(
45-
(result) => {
46-
// drop the file when null is returned
47-
if (result === null) {
48-
cb();
49-
} else {
50-
cb(undefined, result || file);
51-
}
52-
},
53-
(error) => cb(error)
54-
);
55-
},
56-
});
40+
transform(file: File, _, cb) {
41+
Promise.resolve()
42+
.then(async () => {
43+
if (file.isDirectory()) {
44+
return cb(undefined, file);
45+
}
5746

58-
export const excludeFiles = (globs: string[]) => {
59-
const patterns = globs.map(
60-
(g) =>
61-
new Minimatch(g, {
62-
matchBase: true,
63-
})
64-
);
47+
if (!(file.contents instanceof Buffer)) {
48+
throw new Error('files must be buffered to use transformFileStream()');
49+
}
6550

66-
return tapFileStream((file) => {
67-
const path = file.relative.replace(/\.ejs$/, '');
68-
const exclude = patterns.some((p) => p.match(path));
69-
if (exclude) {
70-
return null;
71-
}
51+
const result = await fn(file as BufferedFile);
52+
53+
if (result === null) {
54+
// explicitly drop file if null is returned
55+
cb();
56+
} else {
57+
cb(undefined, result || file);
58+
}
59+
})
60+
.catch(cb);
61+
},
7262
});
73-
};

0 commit comments

Comments
 (0)