diff --git a/.buildkite/scripts/build_kibana.sh b/.buildkite/scripts/build_kibana.sh index 2757c956920f79..6cf21353f35046 100755 --- a/.buildkite/scripts/build_kibana.sh +++ b/.buildkite/scripts/build_kibana.sh @@ -8,14 +8,15 @@ export KBN_NP_PLUGINS_BUILT=true echo "--- Build Kibana Distribution" -BUILD_ARGS="" -is_pr_with_label "ci:build-all-platforms" && BUILD_ARGS="--all-platforms" -is_pr_with_label "ci:build-example-plugins" && BUILD_ARGS="$BUILD_ARGS --example-plugins" -is_pr_with_label "ci:build-docker-cross-compile" && BUILD_ARG="$BUILD_ARGS --docker-cross-compile" -is_pr_with_label "ci:build-os-packages" || BUILD_ARGS="$BUILD_ARGS --skip-os-packages" -is_pr_with_label "ci:build-canvas-shareable-runtime" || BUILD_ARGS="$BUILD_ARGS --skip-canvas-shareable-runtime" -is_pr_with_label "ci:build-docker-contexts" || BUILD_ARGS="$BUILD_ARGS --skip-docker-contexts" -node scripts/build $BUILD_ARGS +BUILD_ARGS=("--with-test-plugins" "--with-example-plugins") +is_pr_with_label "ci:build-all-platforms" && BUILD_ARGS+=("--all-platforms") +is_pr_with_label "ci:build-docker-cross-compile" && BUILD_ARGS+=("--docker-cross-compile") +is_pr_with_label "ci:build-os-packages" || BUILD_ARGS+=("--skip-os-packages") +is_pr_with_label "ci:build-canvas-shareable-runtime" || BUILD_ARGS+=("--skip-canvas-shareable-runtime") +is_pr_with_label "ci:build-docker-contexts" || BUILD_ARGS+=("--skip-docker-contexts") + +echo "> node scripts/build" "${BUILD_ARGS[@]}" +node scripts/build "${BUILD_ARGS[@]}" if is_pr_with_label "ci:build-cloud-image"; then echo "$KIBANA_DOCKER_PASSWORD" | docker login -u "$KIBANA_DOCKER_USERNAME" --password-stdin docker.elastic.co diff --git a/.buildkite/scripts/steps/artifacts/build.sh b/.buildkite/scripts/steps/artifacts/build.sh index 7c18dcb328a283..598bed6919f762 100644 --- a/.buildkite/scripts/steps/artifacts/build.sh +++ b/.buildkite/scripts/steps/artifacts/build.sh @@ -7,7 +7,7 @@ set -euo pipefail source .buildkite/scripts/steps/artifacts/env.sh echo "--- Build Kibana artifacts" -node scripts/build --all-platforms --debug --docker-cross-compile $(echo "$BUILD_ARGS") +node scripts/build --all-platforms --debug --docker-cross-compile "${BUILD_ARGS[@]}" echo "--- Extract default i18n messages" mkdir -p target/i18n diff --git a/.buildkite/scripts/steps/artifacts/docker_context.sh b/.buildkite/scripts/steps/artifacts/docker_context.sh index c50fb3e0524fd9..06efe388cd9314 100755 --- a/.buildkite/scripts/steps/artifacts/docker_context.sh +++ b/.buildkite/scripts/steps/artifacts/docker_context.sh @@ -11,7 +11,7 @@ KIBANA_DOCKER_CONTEXT="${KIBANA_DOCKER_CONTEXT:="default"}" echo "--- Create contexts" mkdir -p target -node scripts/build --skip-initialize --skip-generic-folders --skip-platform-folders --skip-archives --docker-context-use-local-artifact $(echo "$BUILD_ARGS") +node scripts/build --skip-initialize --skip-generic-folders --skip-platform-folders --skip-archives --docker-context-use-local-artifact "${BUILD_ARGS[@]}" echo "--- Setup context" DOCKER_BUILD_FOLDER=$(mktemp -d) diff --git a/.buildkite/scripts/steps/artifacts/env.sh b/.buildkite/scripts/steps/artifacts/env.sh index 3330a66e181e48..15f8f1df9fb280 100755 --- a/.buildkite/scripts/steps/artifacts/env.sh +++ b/.buildkite/scripts/steps/artifacts/env.sh @@ -15,11 +15,11 @@ fi if [[ "$RELEASE_BUILD" == "true" ]]; then FULL_VERSION="$QUALIFIER_VERSION" - BUILD_ARGS="--release --version-qualifier=$VERSION_QUALIFIER" + BUILD_ARGS=("--release" "--version-qualifier=$VERSION_QUALIFIER") WORKFLOW="staging" else FULL_VERSION="$QUALIFIER_VERSION-SNAPSHOT" - BUILD_ARGS="--version-qualifier=$VERSION_QUALIFIER" + BUILD_ARGS=("--version-qualifier=$VERSION_QUALIFIER") WORKFLOW="snapshot" fi diff --git a/.buildkite/scripts/steps/checks.sh b/.buildkite/scripts/steps/checks.sh index 4b7df27e331b83..3272a5184e60b0 100755 --- a/.buildkite/scripts/steps/checks.sh +++ b/.buildkite/scripts/steps/checks.sh @@ -6,6 +6,7 @@ export DISABLE_BOOTSTRAP_VALIDATION=false .buildkite/scripts/bootstrap.sh .buildkite/scripts/steps/checks/precommit_hook.sh +.buildkite/scripts/steps/checks/packages.sh .buildkite/scripts/steps/checks/ts_projects.sh .buildkite/scripts/steps/checks/packages.sh .buildkite/scripts/steps/checks/bazel_packages.sh @@ -18,7 +19,6 @@ export DISABLE_BOOTSTRAP_VALIDATION=false .buildkite/scripts/steps/checks/i18n.sh .buildkite/scripts/steps/checks/file_casing.sh .buildkite/scripts/steps/checks/licenses.sh -.buildkite/scripts/steps/checks/plugins_with_circular_deps.sh .buildkite/scripts/steps/checks/test_projects.sh .buildkite/scripts/steps/checks/test_hardening.sh .buildkite/scripts/steps/checks/ftr_configs.sh diff --git a/.buildkite/scripts/steps/checks/packages.sh b/.buildkite/scripts/steps/checks/packages.sh index ebbbc9057e9a40..fae0011a9fb5ab 100755 --- a/.buildkite/scripts/steps/checks/packages.sh +++ b/.buildkite/scripts/steps/checks/packages.sh @@ -4,7 +4,7 @@ set -euo pipefail source .buildkite/scripts/common/util.sh -echo --- Lint packages +echo --- Lint Packages cmd="node scripts/lint_packages" if is_pr && ! is_auto_commit_disabled; then cmd="$cmd --fix" diff --git a/.buildkite/scripts/steps/checks/plugins_with_circular_deps.sh b/.buildkite/scripts/steps/checks/plugins_with_circular_deps.sh deleted file mode 100755 index a09c09f9fb8479..00000000000000 --- a/.buildkite/scripts/steps/checks/plugins_with_circular_deps.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -source .buildkite/scripts/common/util.sh - -echo --- Check Plugins With Circular Dependencies -node scripts/find_plugins_with_circular_deps diff --git a/.buildkite/scripts/steps/checks/ts_projects.sh b/.buildkite/scripts/steps/checks/ts_projects.sh index 786d88a61d0650..4accfe533cb276 100755 --- a/.buildkite/scripts/steps/checks/ts_projects.sh +++ b/.buildkite/scripts/steps/checks/ts_projects.sh @@ -4,7 +4,7 @@ set -euo pipefail source .buildkite/scripts/common/util.sh -echo --- Lint TS projects +echo --- Lint TS Projects cmd="node scripts/lint_ts_projects" if is_pr && ! is_auto_commit_disabled; then cmd="$cmd --fix" diff --git a/.eslintrc.js b/.eslintrc.js index 4cf7871025f641..703ac3c157e9a6 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -121,7 +121,7 @@ const VENN_DIAGRAM_HEADER = ` /** Packages which should not be included within production code. */ const DEV_PACKAGE_DIRS = getPackages(REPO_ROOT).flatMap((pkg) => - pkg.isDevOnly ? pkg.normalizedRepoRelativeDir : [] + pkg.isDevOnly() ? pkg.normalizedRepoRelativeDir : [] ); /** Directories (at any depth) which include dev-only code. */ @@ -1736,7 +1736,10 @@ module.exports = { * Code inside .buildkite runs separately from everything else in CI, before bootstrap, with ts-node. It needs a few tweaks because of this. */ { - files: 'packages/kbn-{package-*,repo-*,dep-*}/**/*', + files: [ + 'packages/kbn-{package-*,repo-*,dep-*}/**/*', + 'packages/kbn-find-used-node-modules/**/*', + ], rules: { 'max-classes-per-file': 'off', }, diff --git a/.gitignore b/.gitignore index d5747c2cad7e7c..4d3db591bd7584 100644 --- a/.gitignore +++ b/.gitignore @@ -57,6 +57,7 @@ webpackstats.json !/config/serverless.security.yml !/config/node.options coverage +!/test/common/fixtures/plugins/coverage selenium .babel_register_cache.json .webpack.babelcache diff --git a/dev_docs/api_welcome.mdx b/dev_docs/api_welcome.mdx index 43fac7407989d1..4295c4453ea53e 100644 --- a/dev_docs/api_welcome.mdx +++ b/dev_docs/api_welcome.mdx @@ -67,7 +67,7 @@ If that isn't the case, please file an issue, it could be a bug with the system. We are [aware of some performance issues](https://github.com/elastic/elastic-docs/issues/274) with deeply nested, or large APIs. In the short term, the best thing you can do is avoid deeply nested API items. Use interfaces rather than inlined objects. Also consider -adding `serviceFolders` in your kibana.json. This will automatically split your docs up based on which APIs are defined within the service folders. +adding `serviceFolders` in your kibana.jsonc. This will automatically split your docs up based on which APIs are defined within the service folders. They will get built into a doc with an id of `kib${PluginName}${ServiceName}PluginApi`. The data plugin does this, so you can [check that out as an example](https://github.com/elastic/kibana/blob/main/src/plugins/data/kibana.json#L13). diff --git a/dev_docs/operations/operations_landing.mdx b/dev_docs/operations/operations_landing.mdx index 702313906c9e24..37a240a599d2e6 100644 --- a/dev_docs/operations/operations_landing.mdx +++ b/dev_docs/operations/operations_landing.mdx @@ -36,7 +36,6 @@ layout: landing { pageId: "kibDevDocsOpsKbnPm" }, { pageId: "kibDevDocsOpsOptimizer" }, { pageId: "kibDevDocsOpsBabelPreset" }, - { pageId: "kibDevDocsOpsBabelPluginPackageImports" }, { pageId: "kibDevDocsOpsUiSharedDepsNpm" }, { pageId: "kibDevDocsOpsUiSharedDepsSrc" }, { pageId: "kibDevDocsOpsPluginDiscovery" }, diff --git a/examples/bfetch_explorer/kibana.json b/examples/bfetch_explorer/kibana.json deleted file mode 100644 index 0eda11670034c9..00000000000000 --- a/examples/bfetch_explorer/kibana.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "id": "bfetchExplorer", - "kibanaVersion": "kibana", - "version": "0.0.1", - "server": true, - "ui": true, - "owner": { - "name": "App Services", - "githubTeam": "kibana-app-services" - }, - "requiredPlugins": ["bfetch", "developerExamples"], - "optionalPlugins": [], - "requiredBundles": ["kibanaReact"] -} diff --git a/examples/bfetch_explorer/kibana.jsonc b/examples/bfetch_explorer/kibana.jsonc new file mode 100644 index 00000000000000..dbcd5c34963551 --- /dev/null +++ b/examples/bfetch_explorer/kibana.jsonc @@ -0,0 +1,17 @@ +{ + "type": "plugin", + "id": "@kbn/bfetch-explorer-plugin", + "owner": "@elastic/appex-sharedux", + "plugin": { + "id": "bfetchExplorer", + "server": true, + "browser": true, + "requiredPlugins": [ + "bfetch", + "developerExamples" + ], + "requiredBundles": [ + "kibanaReact" + ] + } +} diff --git a/examples/controls_example/kibana.json b/examples/controls_example/kibana.json deleted file mode 100644 index 605714954967d4..00000000000000 --- a/examples/controls_example/kibana.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "id": "controlsExample", - "owner": { - "name": "Kibana Presentation", - "githubTeam": "kibana-presentation" - }, - "version": "1.0.0", - "kibanaVersion": "kibana", - "ui": true, - "requiredPlugins": [ - "controls", - "data", - "developerExamples", - "embeddable", - "navigation", - "presentationUtil" - ] -} diff --git a/examples/controls_example/kibana.jsonc b/examples/controls_example/kibana.jsonc new file mode 100644 index 00000000000000..2a6907e130d2e3 --- /dev/null +++ b/examples/controls_example/kibana.jsonc @@ -0,0 +1,18 @@ +{ + "type": "plugin", + "id": "@kbn/controls-example-plugin", + "owner": "@elastic/kibana-presentation", + "plugin": { + "id": "controlsExample", + "server": false, + "browser": true, + "requiredPlugins": [ + "controls", + "data", + "developerExamples", + "embeddable", + "navigation", + "presentationUtil" + ] + } +} diff --git a/examples/dashboard_embeddable_examples/kibana.json b/examples/dashboard_embeddable_examples/kibana.json deleted file mode 100644 index ba0c4a84836e77..00000000000000 --- a/examples/dashboard_embeddable_examples/kibana.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "id": "dashboardEmbeddableExamples", - "kibanaVersion": "kibana", - "version": "0.0.1", - "server": false, - "ui": true, - "requiredPlugins": [ - "embeddable", - "embeddableExamples", - "dashboard", - "developerExamples", - "kibanaReact" - ], - "owner": { - "name": "Presentation", - "githubTeam": "kibana-presentation" - }, - "description": "Example app that shows how to embed a dashboard in an application", - "optionalPlugins": [] -} diff --git a/examples/dashboard_embeddable_examples/kibana.jsonc b/examples/dashboard_embeddable_examples/kibana.jsonc new file mode 100644 index 00000000000000..9498c710596309 --- /dev/null +++ b/examples/dashboard_embeddable_examples/kibana.jsonc @@ -0,0 +1,18 @@ +{ + "type": "plugin", + "id": "@kbn/dashboard-embeddable-examples-plugin", + "owner": "@elastic/kibana-presentation", + "description": "Example app that shows how to embed a dashboard in an application", + "plugin": { + "id": "dashboardEmbeddableExamples", + "server": false, + "browser": true, + "requiredPlugins": [ + "embeddable", + "embeddableExamples", + "dashboard", + "developerExamples", + "kibanaReact" + ] + } +} diff --git a/examples/data_view_field_editor_example/kibana.json b/examples/data_view_field_editor_example/kibana.json deleted file mode 100644 index 8d079d10fbc729..00000000000000 --- a/examples/data_view_field_editor_example/kibana.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "id": "dataViewFieldEditorExample", - "kibanaVersion": "kibana", - "version": "0.0.1", - "server": false, - "ui": true, - "requiredPlugins": ["data", "dataViewFieldEditor", "developerExamples"], - "optionalPlugins": [], - "requiredBundles": [], - "owner": { - "name": "App Services", - "githubTeam": "kibana-app-services" - }, - "description": "Data view field editor example app" -} diff --git a/examples/data_view_field_editor_example/kibana.jsonc b/examples/data_view_field_editor_example/kibana.jsonc new file mode 100644 index 00000000000000..7ca538fd509e05 --- /dev/null +++ b/examples/data_view_field_editor_example/kibana.jsonc @@ -0,0 +1,16 @@ +{ + "type": "plugin", + "id": "@kbn/data-view-field-editor-example-plugin", + "owner": "@elastic/kibana-app-services", + "description": "Data view field editor example app", + "plugin": { + "id": "dataViewFieldEditorExample", + "server": false, + "browser": true, + "requiredPlugins": [ + "data", + "dataViewFieldEditor", + "developerExamples" + ] + } +} diff --git a/examples/developer_examples/kibana.json b/examples/developer_examples/kibana.json deleted file mode 100644 index a744b53137dc76..00000000000000 --- a/examples/developer_examples/kibana.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "id": "developerExamples", - "owner": { - "name": "Kibana Core", - "githubTeam": "kibana-core" - }, - "kibanaVersion": "kibana", - "version": "0.0.1", - "ui": true -} diff --git a/examples/developer_examples/kibana.jsonc b/examples/developer_examples/kibana.jsonc new file mode 100644 index 00000000000000..8a6a8068418aa0 --- /dev/null +++ b/examples/developer_examples/kibana.jsonc @@ -0,0 +1,10 @@ +{ + "type": "plugin", + "id": "@kbn/developer-examples-plugin", + "owner": "@elastic/appex-sharedux", + "plugin": { + "id": "developerExamples", + "server": false, + "browser": true + } +} diff --git a/examples/embeddable_examples/kibana.json b/examples/embeddable_examples/kibana.json deleted file mode 100644 index 103857804b5d4a..00000000000000 --- a/examples/embeddable_examples/kibana.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "id": "embeddableExamples", - "kibanaVersion": "kibana", - "version": "0.0.1", - "server": true, - "ui": true, - "owner": { - "name": "App Services", - "githubTeam": "kibana-app-services" - }, - "description": "Example app that shows how to register custom embeddables", - "requiredPlugins": ["embeddable", "uiActions", "savedObjects", "dashboard", "kibanaUtils"], - "optionalPlugins": [], - "extraPublicDirs": ["public/todo", "public/hello_world", "public/todo/todo_ref_embeddable"], - "requiredBundles": ["kibanaReact"] -} diff --git a/examples/embeddable_examples/kibana.jsonc b/examples/embeddable_examples/kibana.jsonc new file mode 100644 index 00000000000000..afc3fbc2706eef --- /dev/null +++ b/examples/embeddable_examples/kibana.jsonc @@ -0,0 +1,26 @@ +{ + "type": "plugin", + "id": "@kbn/embeddable-examples-plugin", + "owner": "@elastic/kibana-presentation", + "description": "Example app that shows how to register custom embeddables", + "plugin": { + "id": "embeddableExamples", + "server": true, + "browser": true, + "requiredPlugins": [ + "embeddable", + "uiActions", + "savedObjects", + "dashboard", + "kibanaUtils" + ], + "requiredBundles": [ + "kibanaReact" + ], + "extraPublicDirs": [ + "public/todo", + "public/hello_world", + "public/todo/todo_ref_embeddable" + ] + } +} diff --git a/examples/embeddable_explorer/kibana.json b/examples/embeddable_explorer/kibana.json deleted file mode 100644 index 6ca12eb2e2a30b..00000000000000 --- a/examples/embeddable_explorer/kibana.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "id": "embeddableExplorer", - "kibanaVersion": "kibana", - "version": "0.0.1", - "server": false, - "ui": true, - "owner": { - "name": "App Services", - "githubTeam": "kibana-app-services" - }, - "description": "Example app that relies on registered functionality in the embeddable_examples plugin", - "requiredPlugins": [ - "uiActions", - "inspector", - "embeddable", - "embeddableExamples", - "developerExamples", - "dashboard", - "kibanaReact", - "savedObjects" - ], - "optionalPlugins": [] -} diff --git a/examples/embeddable_explorer/kibana.jsonc b/examples/embeddable_explorer/kibana.jsonc new file mode 100644 index 00000000000000..1c00e25d7f4285 --- /dev/null +++ b/examples/embeddable_explorer/kibana.jsonc @@ -0,0 +1,21 @@ +{ + "type": "plugin", + "id": "@kbn/embeddable-explorer-plugin", + "owner": "@elastic/kibana-presentation", + "description": "Example app that relies on registered functionality in the embeddable_examples plugin", + "plugin": { + "id": "embeddableExplorer", + "server": false, + "browser": true, + "requiredPlugins": [ + "uiActions", + "inspector", + "embeddable", + "embeddableExamples", + "developerExamples", + "dashboard", + "kibanaReact", + "savedObjects" + ] + } +} diff --git a/examples/expressions_explorer/kibana.json b/examples/expressions_explorer/kibana.json deleted file mode 100644 index dea706d024941b..00000000000000 --- a/examples/expressions_explorer/kibana.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "id": "expressionsExplorer", - "kibanaVersion": "kibana", - "version": "0.0.1", - "server": false, - "ui": true, - "owner": { - "name": "App Services", - "githubTeam": "kibana-app-services" - }, - "requiredPlugins": ["expressions", "inspector", "uiActions", "developerExamples"], - "optionalPlugins": [], - "requiredBundles": ["kibanaReact"] -} diff --git a/examples/expressions_explorer/kibana.jsonc b/examples/expressions_explorer/kibana.jsonc new file mode 100644 index 00000000000000..b389593c161d6e --- /dev/null +++ b/examples/expressions_explorer/kibana.jsonc @@ -0,0 +1,19 @@ +{ + "type": "plugin", + "id": "@kbn/expressions-explorer-plugin", + "owner": "@elastic/kibana-app-services", + "plugin": { + "id": "expressionsExplorer", + "server": false, + "browser": true, + "requiredPlugins": [ + "expressions", + "inspector", + "uiActions", + "developerExamples" + ], + "requiredBundles": [ + "kibanaReact" + ] + } +} diff --git a/examples/field_formats_example/kibana.json b/examples/field_formats_example/kibana.json deleted file mode 100644 index e3bca1b2fcb9ee..00000000000000 --- a/examples/field_formats_example/kibana.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "fieldFormatsExample", - "version": "1.0.0", - "kibanaVersion": "kibana", - "ui": true, - "server": true, - "owner": { - "name": "App Services", - "githubTeam": "kibana-app-services" - }, - "description": "A plugin that demonstrates field formats usage", - "requiredPlugins": ["developerExamples", "fieldFormats", "dataViewFieldEditor", "data"] -} diff --git a/examples/field_formats_example/kibana.jsonc b/examples/field_formats_example/kibana.jsonc new file mode 100644 index 00000000000000..9d85483fb4a8fc --- /dev/null +++ b/examples/field_formats_example/kibana.jsonc @@ -0,0 +1,17 @@ +{ + "type": "plugin", + "id": "@kbn/field-formats-example-plugin", + "owner": "@elastic/kibana-data-discovery", + "description": "A plugin that demonstrates field formats usage", + "plugin": { + "id": "fieldFormatsExample", + "server": true, + "browser": true, + "requiredPlugins": [ + "developerExamples", + "fieldFormats", + "dataViewFieldEditor", + "data" + ] + } +} diff --git a/examples/files_example/kibana.json b/examples/files_example/kibana.json deleted file mode 100644 index b9cc4027a43f45..00000000000000 --- a/examples/files_example/kibana.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "id": "filesExample", - "version": "1.0.0", - "kibanaVersion": "kibana", - "owner": { - "name": "kibana-app-services", - "githubTeam": "kibana-app-services" - }, - "description": "Example plugin integrating with files plugin", - "server": true, - "ui": true, - "requiredPlugins": ["files", "developerExamples"], - "optionalPlugins": [] -} diff --git a/examples/files_example/kibana.jsonc b/examples/files_example/kibana.jsonc new file mode 100644 index 00000000000000..973fd48e9f3e0c --- /dev/null +++ b/examples/files_example/kibana.jsonc @@ -0,0 +1,15 @@ +{ + "type": "plugin", + "id": "@kbn/files-example-plugin", + "owner": "@elastic/appex-sharedux", + "description": "Example plugin integrating with files plugin", + "plugin": { + "id": "filesExample", + "server": true, + "browser": true, + "requiredPlugins": [ + "files", + "developerExamples" + ] + } +} diff --git a/examples/guided_onboarding_example/kibana.json b/examples/guided_onboarding_example/kibana.json deleted file mode 100755 index 57466230ca5e76..00000000000000 --- a/examples/guided_onboarding_example/kibana.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "id": "guidedOnboardingExample", - "version": "1.0.0", - "kibanaVersion": "kibana", - "owner": { - "name": "platform-onboarding", - "githubTeam": "platform-onboarding" - }, - "description": "Example plugin to consume guidedOnboarding", - "server": true, - "ui": true, - "requiredPlugins": ["navigation", "guidedOnboarding"], - "optionalPlugins": [] -} diff --git a/examples/guided_onboarding_example/kibana.jsonc b/examples/guided_onboarding_example/kibana.jsonc new file mode 100644 index 00000000000000..e0fe519a29ff3b --- /dev/null +++ b/examples/guided_onboarding_example/kibana.jsonc @@ -0,0 +1,15 @@ +{ + "type": "plugin", + "id": "@kbn/guided-onboarding-example-plugin", + "owner": "@elastic/platform-onboarding", + "description": "Example plugin to consume guidedOnboarding", + "plugin": { + "id": "guidedOnboardingExample", + "server": true, + "browser": true, + "requiredPlugins": [ + "navigation", + "guidedOnboarding" + ] + } +} diff --git a/examples/hello_world/kibana.json b/examples/hello_world/kibana.json deleted file mode 100644 index d3de28c2cbd7a6..00000000000000 --- a/examples/hello_world/kibana.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "id": "helloWorld", - "version": "1.0.0", - "kibanaVersion": "kibana", - "ui": true, - "owner": { - "name": "Kibana core", - "githubTeam": "kibana-core" - }, - "description": "A plugin which registers a very simple hello world application.", - "requiredPlugins": ["developerExamples"] -} diff --git a/examples/hello_world/kibana.jsonc b/examples/hello_world/kibana.jsonc new file mode 100644 index 00000000000000..4253084c38fac3 --- /dev/null +++ b/examples/hello_world/kibana.jsonc @@ -0,0 +1,14 @@ +{ + "type": "plugin", + "id": "@kbn/hello-world-plugin", + "owner": "@elastic/kibana-core", + "description": "A plugin which registers a very simple hello world application.", + "plugin": { + "id": "helloWorld", + "server": false, + "browser": true, + "requiredPlugins": [ + "developerExamples" + ] + } +} diff --git a/examples/locator_examples/kibana.json b/examples/locator_examples/kibana.json deleted file mode 100644 index a288bf5608c53f..00000000000000 --- a/examples/locator_examples/kibana.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "id": "locatorExamples", - "kibanaVersion": "kibana", - "version": "0.0.1", - "server": false, - "ui": true, - "owner": { - "name": "App Services", - "githubTeam": "kibana-app-services" - }, - "description": "Example app that registers custom URL locators", - "requiredPlugins": ["share"], - "optionalPlugins": [], - "extraPublicDirs": ["public/locator"] -} diff --git a/examples/locator_examples/kibana.jsonc b/examples/locator_examples/kibana.jsonc new file mode 100644 index 00000000000000..55d414be65dbc3 --- /dev/null +++ b/examples/locator_examples/kibana.jsonc @@ -0,0 +1,17 @@ +{ + "type": "plugin", + "id": "@kbn/locator-examples-plugin", + "owner": "@elastic/kibana-app-services", + "description": "Example app that registers custom URL locators", + "plugin": { + "id": "locatorExamples", + "server": false, + "browser": true, + "requiredPlugins": [ + "share" + ], + "extraPublicDirs": [ + "public/locator" + ] + } +} diff --git a/examples/locator_explorer/kibana.json b/examples/locator_explorer/kibana.json deleted file mode 100644 index 7f00461ccf7741..00000000000000 --- a/examples/locator_explorer/kibana.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "id": "locatorExplorer", - "kibanaVersion": "kibana", - "version": "0.0.1", - "server": false, - "ui": true, - "owner": { - "name": "App Services", - "githubTeam": "kibana-app-services" - }, - "description": "Example app that shows how to use custom URL locators", - "requiredPlugins": ["share", "locatorExamples", "developerExamples"], - "optionalPlugins": [] -} diff --git a/examples/locator_explorer/kibana.jsonc b/examples/locator_explorer/kibana.jsonc new file mode 100644 index 00000000000000..0abf95d63440cc --- /dev/null +++ b/examples/locator_explorer/kibana.jsonc @@ -0,0 +1,16 @@ +{ + "type": "plugin", + "id": "@kbn/locator-explorer-plugin", + "owner": "@elastic/kibana-app-services", + "description": "Example app that shows how to use custom URL locators", + "plugin": { + "id": "locatorExplorer", + "server": false, + "browser": true, + "requiredPlugins": [ + "share", + "locatorExamples", + "developerExamples" + ] + } +} diff --git a/examples/partial_results_example/kibana.json b/examples/partial_results_example/kibana.json deleted file mode 100644 index 1fe46e55d4039f..00000000000000 --- a/examples/partial_results_example/kibana.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "id": "paertialResultsExample", - "version": "0.1.0", - "kibanaVersion": "kibana", - "ui": true, - "owner": { - "name": "App Services", - "githubTeam": "kibana-app-services" - }, - "description": "A plugin demonstrating partial results in the expressions plugin", - "requiredPlugins": ["developerExamples", "expressions"] -} diff --git a/examples/partial_results_example/kibana.jsonc b/examples/partial_results_example/kibana.jsonc new file mode 100644 index 00000000000000..a050cd491936cf --- /dev/null +++ b/examples/partial_results_example/kibana.jsonc @@ -0,0 +1,15 @@ +{ + "type": "plugin", + "id": "@kbn/paertial-results-example-plugin", + "owner": "@elastic/kibana-data-discovery", + "description": "A plugin demonstrating partial results in the expressions plugin", + "plugin": { + "id": "paertialResultsExample", + "server": false, + "browser": true, + "requiredPlugins": [ + "developerExamples", + "expressions" + ] + } +} diff --git a/examples/preboot_example/kibana.json b/examples/preboot_example/kibana.json deleted file mode 100644 index 39bdfc5447dd50..00000000000000 --- a/examples/preboot_example/kibana.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "id": "prebootExample", - "kibanaVersion": "kibana", - "owner": { - "name": "Core", - "githubTeam": "kibana-core" - }, - "description": "The example of the `preboot` plugin.", - "version": "8.0.0", - "configPath": ["prebootExample"], - "type": "preboot", - "server": true, - "ui": true, - "requiredPlugins": [], - "requiredBundles": [] -} diff --git a/examples/preboot_example/kibana.jsonc b/examples/preboot_example/kibana.jsonc new file mode 100644 index 00000000000000..41b388e239dc31 --- /dev/null +++ b/examples/preboot_example/kibana.jsonc @@ -0,0 +1,18 @@ +{ + "type": "plugin", + "id": "@kbn/preboot-example-plugin", + "owner": [ + "@elastic/kibana-security", + "@elastic/kibana-core" + ], + "description": "The example of the `preboot` plugin.", + "plugin": { + "id": "prebootExample", + "type": "preboot", + "server": true, + "browser": true, + "configPath": [ + "prebootExample" + ] + } +} diff --git a/examples/response_stream/kibana.json b/examples/response_stream/kibana.json deleted file mode 100644 index 070c90b1c1ebd2..00000000000000 --- a/examples/response_stream/kibana.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "id": "responseStream", - "kibanaVersion": "kibana", - "version": "0.0.1", - "server": true, - "ui": true, - "owner": { - "name": "ML UI", - "githubTeam": "ml-ui" - }, - "requiredPlugins": ["developerExamples"], - "optionalPlugins": [], - "requiredBundles": ["kibanaReact"] -} diff --git a/examples/response_stream/kibana.jsonc b/examples/response_stream/kibana.jsonc new file mode 100644 index 00000000000000..340e1f7c0a657b --- /dev/null +++ b/examples/response_stream/kibana.jsonc @@ -0,0 +1,16 @@ +{ + "type": "plugin", + "id": "@kbn/response-stream-plugin", + "owner": "@elastic/ml-ui", + "plugin": { + "id": "responseStream", + "server": true, + "browser": true, + "requiredPlugins": [ + "developerExamples" + ], + "requiredBundles": [ + "kibanaReact" + ] + } +} diff --git a/examples/routing_example/kibana.json b/examples/routing_example/kibana.json deleted file mode 100644 index a2e55901d80b2f..00000000000000 --- a/examples/routing_example/kibana.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "id": "routingExample", - "kibanaVersion": "kibana", - "version": "0.0.1", - "server": true, - "ui": true, - "owner": { - "name": "Core", - "githubTeam": "kibana-core" - }, - "description": "A simple example of how to use core's routing services", - "requiredPlugins": ["developerExamples"], - "optionalPlugins": [] -} diff --git a/examples/routing_example/kibana.jsonc b/examples/routing_example/kibana.jsonc new file mode 100644 index 00000000000000..46018a4f59f186 --- /dev/null +++ b/examples/routing_example/kibana.jsonc @@ -0,0 +1,14 @@ +{ + "type": "plugin", + "id": "@kbn/routing-example-plugin", + "owner": "@elastic/kibana-core", + "description": "A simple example of how to use core's routing services", + "plugin": { + "id": "routingExample", + "server": true, + "browser": true, + "requiredPlugins": [ + "developerExamples" + ] + } +} diff --git a/examples/screenshot_mode_example/kibana.json b/examples/screenshot_mode_example/kibana.json deleted file mode 100644 index 66acfe0c931d5e..00000000000000 --- a/examples/screenshot_mode_example/kibana.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "id": "screenshotModeExample", - "kibanaVersion": "kibana", - "version": "1.0.0", - "server": true, - "ui": true, - "owner": { - "name": "App Services", - "githubTeam": "kibana-app-services" - }, - "description": "Example plugin of how to use screenshotMode plugin services", - "requiredPlugins": ["navigation", "screenshotMode", "usageCollection", "developerExamples"], - "optionalPlugins": [] -} diff --git a/examples/screenshot_mode_example/kibana.jsonc b/examples/screenshot_mode_example/kibana.jsonc new file mode 100644 index 00000000000000..086d2e2b37359d --- /dev/null +++ b/examples/screenshot_mode_example/kibana.jsonc @@ -0,0 +1,17 @@ +{ + "type": "plugin", + "id": "@kbn/screenshot-mode-example-plugin", + "owner": "@elastic/kibana-app-services", + "description": "Example plugin of how to use screenshotMode plugin services", + "plugin": { + "id": "screenshotModeExample", + "server": true, + "browser": true, + "requiredPlugins": [ + "navigation", + "screenshotMode", + "usageCollection", + "developerExamples" + ] + } +} diff --git a/examples/search_examples/kibana.json b/examples/search_examples/kibana.json deleted file mode 100644 index ac6f51727974b1..00000000000000 --- a/examples/search_examples/kibana.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "id": "searchExamples", - "kibanaVersion": "kibana", - "version": "0.0.1", - "owner": { - "name": "App Services", - "githubTeam": "kibana-app-services" - }, - "description": "Example plugin of how to use data plugin search services", - "server": true, - "ui": true, - "requiredPlugins": ["navigation", "data", "developerExamples", "inspector", "kibanaUtils", "share", "unifiedSearch"], - "optionalPlugins": [], - "requiredBundles": ["kibanaReact"], - "owner": { - "name": "App Services", - "githubTeam": "kibana-app-services" - }, - "description": "Examples for using the data plugin search service. Includes examples for searching using the high level search source, or low-level search services, as well as integrating with search sessions." -} diff --git a/examples/search_examples/kibana.jsonc b/examples/search_examples/kibana.jsonc new file mode 100644 index 00000000000000..8fc8c271ee5217 --- /dev/null +++ b/examples/search_examples/kibana.jsonc @@ -0,0 +1,23 @@ +{ + "type": "plugin", + "id": "@kbn/search-examples-plugin", + "owner": "@elastic/kibana-data-discovery", + "description": "Examples for using the data plugin search service. Includes examples for searching using the high level search source, or low-level search services, as well as integrating with search sessions.", + "plugin": { + "id": "searchExamples", + "server": true, + "browser": true, + "requiredPlugins": [ + "navigation", + "data", + "developerExamples", + "inspector", + "kibanaUtils", + "share", + "unifiedSearch" + ], + "requiredBundles": [ + "kibanaReact" + ] + } +} diff --git a/examples/share_examples/kibana.json b/examples/share_examples/kibana.json deleted file mode 100644 index ac2157ad97b28b..00000000000000 --- a/examples/share_examples/kibana.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "id": "shareExamples", - "kibanaVersion": "kibana", - "version": "0.0.1", - "server": false, - "ui": true, - "owner": { - "name": "App Services", - "githubTeam": "kibana-app-services" - }, - "description": "Small demos of share plugin usage", - "requiredPlugins": ["share"], - "optionalPlugins": [] -} diff --git a/examples/share_examples/kibana.jsonc b/examples/share_examples/kibana.jsonc new file mode 100644 index 00000000000000..77d022524d3225 --- /dev/null +++ b/examples/share_examples/kibana.jsonc @@ -0,0 +1,14 @@ +{ + "type": "plugin", + "id": "@kbn/share-examples-plugin", + "owner": "@elastic/kibana-app-services", + "description": "Small demos of share plugin usage", + "plugin": { + "id": "shareExamples", + "server": false, + "browser": true, + "requiredPlugins": [ + "share" + ] + } +} diff --git a/examples/state_containers_examples/kibana.json b/examples/state_containers_examples/kibana.json deleted file mode 100644 index 780732ab930c4f..00000000000000 --- a/examples/state_containers_examples/kibana.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "id": "stateContainersExamples", - "kibanaVersion": "kibana", - "version": "0.0.1", - "owner": { - "name": "App Services", - "githubTeam": "kibana-app-services" - }, - "description": "Example plugin of how to use kibanaUtils services", - "server": false, - "ui": true, - "requiredPlugins": ["navigation", "data", "developerExamples"], - "optionalPlugins": [], - "requiredBundles": ["kibanaUtils"] -} diff --git a/examples/state_containers_examples/kibana.jsonc b/examples/state_containers_examples/kibana.jsonc new file mode 100644 index 00000000000000..b1c2c34856ba07 --- /dev/null +++ b/examples/state_containers_examples/kibana.jsonc @@ -0,0 +1,19 @@ +{ + "type": "plugin", + "id": "@kbn/state-containers-examples-plugin", + "owner": "@elastic/appex-sharedux", + "description": "Example plugin of how to use kibanaUtils services", + "plugin": { + "id": "stateContainersExamples", + "server": false, + "browser": true, + "requiredPlugins": [ + "navigation", + "data", + "developerExamples" + ], + "requiredBundles": [ + "kibanaUtils" + ] + } +} diff --git a/examples/ui_action_examples/kibana.json b/examples/ui_action_examples/kibana.json deleted file mode 100644 index 717414fc513bd3..00000000000000 --- a/examples/ui_action_examples/kibana.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "id": "uiActionsExamples", - "kibanaVersion": "kibana", - "version": "0.0.1", - "owner": { - "name": "App Services", - "githubTeam": "kibana-app-services" - }, - "description": "Example plugin of how to register custom uiActions", - "server": false, - "ui": true, - "requiredPlugins": ["uiActions"], - "optionalPlugins": [], - "requiredBundles": ["kibanaReact"] -} diff --git a/examples/ui_action_examples/kibana.jsonc b/examples/ui_action_examples/kibana.jsonc new file mode 100644 index 00000000000000..3de8e301bae2e3 --- /dev/null +++ b/examples/ui_action_examples/kibana.jsonc @@ -0,0 +1,17 @@ +{ + "type": "plugin", + "id": "@kbn/ui-actions-examples-plugin", + "owner": "@elastic/appex-sharedux", + "description": "Example plugin of how to register custom uiActions", + "plugin": { + "id": "uiActionsExamples", + "server": false, + "browser": true, + "requiredPlugins": [ + "uiActions" + ], + "requiredBundles": [ + "kibanaReact" + ] + } +} diff --git a/examples/ui_actions_explorer/kibana.json b/examples/ui_actions_explorer/kibana.json deleted file mode 100644 index 7d853a6042febe..00000000000000 --- a/examples/ui_actions_explorer/kibana.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "id": "uiActionsExplorer", - "kibanaVersion": "kibana", - "version": "0.0.1", - "owner": { - "name": "App Services", - "githubTeam": "kibana-app-services" - }, - "description": "Example plugin of how to use uiActions plugin services", - "server": false, - "ui": true, - "requiredPlugins": ["uiActions", "uiActionsExamples", "developerExamples"], - "optionalPlugins": [], - "requiredBundles": ["kibanaReact"] -} diff --git a/examples/ui_actions_explorer/kibana.jsonc b/examples/ui_actions_explorer/kibana.jsonc new file mode 100644 index 00000000000000..e6c2c188c2f976 --- /dev/null +++ b/examples/ui_actions_explorer/kibana.jsonc @@ -0,0 +1,19 @@ +{ + "type": "plugin", + "id": "@kbn/ui-actions-explorer-plugin", + "owner": "@elastic/appex-sharedux", + "description": "Example plugin of how to use uiActions plugin services", + "plugin": { + "id": "uiActionsExplorer", + "server": false, + "browser": true, + "requiredPlugins": [ + "uiActions", + "uiActionsExamples", + "developerExamples" + ], + "requiredBundles": [ + "kibanaReact" + ] + } +} diff --git a/examples/user_profile_examples/kibana.json b/examples/user_profile_examples/kibana.json deleted file mode 100644 index c808302fe65a73..00000000000000 --- a/examples/user_profile_examples/kibana.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "id": "userProfileExamples", - "kibanaVersion": "kibana", - "version": "0.0.1", - "server": true, - "ui": true, - "owner": { - "name": "Kibana Platform Security", - "githubTeam": "kibana-security" - }, - "description": "Demo of how to implement a suggest user functionality", - "requiredPlugins": ["developerExamples", "security", "spaces"], - "optionalPlugins": [] -} diff --git a/examples/user_profile_examples/kibana.jsonc b/examples/user_profile_examples/kibana.jsonc new file mode 100644 index 00000000000000..1a8c7dace2cef0 --- /dev/null +++ b/examples/user_profile_examples/kibana.jsonc @@ -0,0 +1,16 @@ +{ + "type": "plugin", + "id": "@kbn/user-profile-examples-plugin", + "owner": "@elastic/kibana-security", + "description": "Demo of how to implement a suggest user functionality", + "plugin": { + "id": "userProfileExamples", + "server": true, + "browser": true, + "requiredPlugins": [ + "developerExamples", + "security", + "spaces" + ] + } +} diff --git a/kbn_pm/src/cli.mjs b/kbn_pm/src/cli.mjs index 376369cd983326..34404f8b0ab570 100644 --- a/kbn_pm/src/cli.mjs +++ b/kbn_pm/src/cli.mjs @@ -23,7 +23,7 @@ import { Log } from './lib/log.mjs'; import External from './lib/external_packages.js'; const start = Date.now(); -const args = new Args(process.argv.slice(2), process.env.CI ? ['--quiet'] : []); +const args = new Args(process.argv.slice(2), []); const log = new Log(args.getLoggingLevel()); const cmdName = args.getCommandName(); diff --git a/kbn_pm/src/commands/bootstrap/bootstrap_command.mjs b/kbn_pm/src/commands/bootstrap/bootstrap_command.mjs index 0c3731cafe35b5..d473e293cd2c54 100644 --- a/kbn_pm/src/commands/bootstrap/bootstrap_command.mjs +++ b/kbn_pm/src/commands/bootstrap/bootstrap_command.mjs @@ -62,7 +62,7 @@ export const command = { const forceInstall = args.getBooleanValue('force-install') ?? (await haveNodeModulesBeenManuallyDeleted()); - const [{ packages, plugins, tsConfigsPaths }] = await Promise.all([ + const [{ packageManifestPaths, tsConfigRepoRels }] = await Promise.all([ // discover the location of packages, plugins, etc await time('discovery', discovery), @@ -78,16 +78,22 @@ export const command = { ]); // generate the package map and package.json file, if necessary - await Promise.all([ + const [packages] = await Promise.all([ time('regenerate package map', async () => { - await regeneratePackageMap(packages, plugins, log); + return await regeneratePackageMap(log, packageManifestPaths); }), time('regenerate tsconfig map', async () => { - await regenerateTsconfigPaths(tsConfigsPaths, log); + await regenerateTsconfigPaths(tsConfigRepoRels, log); }), + ]); + + await Promise.all([ time('update package json', async () => { await updatePackageJson(packages, log); }), + time('regenerate tsconfig.base.json', async () => { + await regenerateBaseTsconfig(packages, log); + }), ]); // Bootstrap process for Bazel packages @@ -111,9 +117,6 @@ export const command = { }); await Promise.all([ - time('regenerate tsconfig.base.json', async () => { - await regenerateBaseTsconfig(); - }), time('sort package json', async () => { await sortPackageJson(log); }), diff --git a/kbn_pm/src/commands/bootstrap/discovery.mjs b/kbn_pm/src/commands/bootstrap/discovery.mjs index fad8a283700175..0baf594f436400 100644 --- a/kbn_pm/src/commands/bootstrap/discovery.mjs +++ b/kbn_pm/src/commands/bootstrap/discovery.mjs @@ -7,83 +7,37 @@ */ import Path from 'path'; -import Fs from 'fs'; -import ChildProcess from 'child_process'; -import { promisify } from 'util'; +import External from '../../lib/external_packages.js'; import { REPO_ROOT } from '../../lib/paths.mjs'; -const execAsync = promisify(ChildProcess.execFile); export async function discovery() { - const { getPluginSearchPaths, simpleKibanaPlatformPluginDiscovery } = await import( - // eslint-disable-next-line @kbn/imports/uniform_imports - '../../../../packages/kbn-plugin-discovery/index.js' - ); - - const { Package } = await import( - // we need to run this before we install node modules, so it can't rely on @kbn/* imports - // eslint-disable-next-line @kbn/imports/uniform_imports - '../../../../packages/kbn-repo-packages/index.js' - ); - - const proc = await execAsync('git', ['ls-files', '-comt', '--exclude-standard'], { - cwd: REPO_ROOT, - encoding: 'utf8', - maxBuffer: Infinity, - }); - - const paths = new Map(); - /** @type {Map>} */ - const filesByName = new Map(); - - for (const raw of proc.stdout.split('\n')) { - const line = raw.trim(); - if (!line) { + const { getRepoRels } = External['@kbn/repo-packages'](); + + /** @type {string[]} */ + const tsConfigRepoRels = []; + /** @type {string[]} */ + const packageManifestPaths = []; + for (const repoRel of await getRepoRels(REPO_ROOT, [ + 'tsconfig.json', + '**/tsconfig.json', + '**/kibana.jsonc', + ])) { + if (repoRel === 'tsconfig.json' || repoRel.endsWith('/tsconfig.json')) { + tsConfigRepoRels.push(repoRel); continue; } - const repoRel = line.slice(2); // trim the single char status and separating space from the line - const name = repoRel.split('/').pop(); - if (name !== 'kibana.jsonc' && name !== 'tsconfig.json') { + if (repoRel.endsWith('/kibana.jsonc')) { + packageManifestPaths.push(Path.resolve(REPO_ROOT, repoRel)); continue; } - const existingPath = paths.get(repoRel); - const path = existingPath ?? Path.resolve(REPO_ROOT, repoRel); - if (!existingPath) { - paths.set(repoRel, path); - } - - let files = filesByName.get(name); - if (!files) { - files = new Set(); - filesByName.set(name, files); - } - - if (line.startsWith('C ')) { - // this line indicates that the previous path is changed in the working - // tree, so we need to determine if it was deleted and remove it if so - if (!Fs.existsSync(path)) { - files.delete(path); - } - } else { - files.add(path); - } + throw new Error(`unexpected repo rel: ${repoRel}`); } return { - plugins: simpleKibanaPlatformPluginDiscovery( - getPluginSearchPaths({ - rootDir: REPO_ROOT, - examples: true, - oss: false, - testPlugins: true, - }), - [] - ), - tsConfigsPaths: Array.from(filesByName.get('tsconfig.json') ?? new Set()), - packages: Array.from(filesByName.get('kibana.jsonc') ?? new Set()) - .map((path) => Package.fromManifest(REPO_ROOT, path)) - .sort((a, b) => a.id.localeCompare(b.id)), + tsConfigRepoRels, + packageManifestPaths, }; } diff --git a/kbn_pm/src/commands/bootstrap/regenerate_base_tsconfig.mjs b/kbn_pm/src/commands/bootstrap/regenerate_base_tsconfig.mjs index 3b0d8fdcbc0602..a0ae7170ab53ef 100644 --- a/kbn_pm/src/commands/bootstrap/regenerate_base_tsconfig.mjs +++ b/kbn_pm/src/commands/bootstrap/regenerate_base_tsconfig.mjs @@ -10,27 +10,30 @@ import Path from 'path'; import Fsp from 'fs/promises'; import { REPO_ROOT } from '../../lib/paths.mjs'; -import External from '../../lib/external_packages.js'; -export async function regenerateBaseTsconfig() { - const pkgMap = External['@kbn/repo-packages']().readPackageMap(); +/** + * @param {import('@kbn/repo-packages').Package[]} packages + * @param {import('@kbn/some-dev-log').SomeDevLog} log + */ +export async function regenerateBaseTsconfig(packages, log) { const tsconfigPath = Path.resolve(REPO_ROOT, 'tsconfig.base.json'); - const lines = (await Fsp.readFile(tsconfigPath, 'utf-8')).split('\n'); + const current = await Fsp.readFile(tsconfigPath, 'utf8'); + const lines = current.split('\n'); const start = lines.findIndex((l) => l.trim() === '// START AUTOMATED PACKAGE LISTING'); const end = lines.findIndex((l) => l.trim() === '// END AUTOMATED PACKAGE LISTING'); - const current = await Fsp.readFile(tsconfigPath, 'utf8'); const updated = [ ...lines.slice(0, start + 1), - ...Array.from(pkgMap.entries()).flatMap(([moduleId, repoRelPath]) => [ - ` "${moduleId}": ["${repoRelPath}"],`, - ` "${moduleId}/*": ["${repoRelPath}/*"],`, + ...packages.flatMap((p) => [ + ` "${p.id}": ["${p.normalizedRepoRelativeDir}"],`, + ` "${p.id}/*": ["${p.normalizedRepoRelativeDir}/*"],`, ]), ...lines.slice(end), ].join('\n'); if (updated !== current) { await Fsp.writeFile(tsconfigPath, updated); + log.warning('updated tsconfig.base.json'); } } diff --git a/kbn_pm/src/commands/bootstrap/regenerate_package_map.mjs b/kbn_pm/src/commands/bootstrap/regenerate_package_map.mjs index 167f14fc8b9516..285dba13b0fe2b 100644 --- a/kbn_pm/src/commands/bootstrap/regenerate_package_map.mjs +++ b/kbn_pm/src/commands/bootstrap/regenerate_package_map.mjs @@ -6,46 +6,19 @@ * Side Public License, v 1. */ -import Path from 'path'; -import Fs from 'fs'; -import Fsp from 'fs/promises'; - -import { convertPluginIdToPackageId } from '../../lib/plugins.mjs'; -import { normalizePath } from '../../lib/normalize_path.mjs'; +import External from '../../lib/external_packages.js'; import { REPO_ROOT } from '../../lib/paths.mjs'; /** - * - * @param {import('@kbn/repo-packages').Package[]} packages - * @param {import('@kbn/plugin-discovery').KibanaPlatformPlugin[]} plugins * @param {import('@kbn/some-dev-log').SomeDevLog} log + * @param {string[]} packageManifestPaths */ -export async function regeneratePackageMap(packages, plugins, log) { - const path = Path.resolve(REPO_ROOT, 'packages/kbn-repo-packages/package-map.json'); - const existingContent = Fs.existsSync(path) ? await Fsp.readFile(path, 'utf8') : undefined; - - /** @type {Array<[string, string]>} */ - const entries = [['@kbn/core', 'src/core']]; - - for (const pkg of packages) { - entries.push([pkg.manifest.id, pkg.normalizedRepoRelativeDir]); - } +export async function regeneratePackageMap(log, packageManifestPaths) { + const { updatePackageMap, getPackages } = External['@kbn/repo-packages'](); - for (const plugin of plugins) { - entries.push([ - convertPluginIdToPackageId(plugin.manifest.id), - normalizePath(Path.relative(REPO_ROOT, plugin.directory)), - ]); - } - - const content = JSON.stringify( - entries.sort((a, b) => a[0].localeCompare(b[0])), - null, - 2 - ); - - if (content !== existingContent) { - await Fsp.writeFile(path, content); + if (updatePackageMap(REPO_ROOT, packageManifestPaths)) { log.warning('updated package map'); } + + return getPackages(REPO_ROOT); } diff --git a/kbn_pm/src/commands/bootstrap/regenerate_tsconfig_paths.mjs b/kbn_pm/src/commands/bootstrap/regenerate_tsconfig_paths.mjs index 20d97e52097455..06e32a50729196 100644 --- a/kbn_pm/src/commands/bootstrap/regenerate_tsconfig_paths.mjs +++ b/kbn_pm/src/commands/bootstrap/regenerate_tsconfig_paths.mjs @@ -13,16 +13,14 @@ import Fsp from 'fs/promises'; import { REPO_ROOT } from '../../lib/paths.mjs'; /** - * @param {string[]} tsconfigPaths + * @param {string[]} tsConfigRepoRels * @param {import('@kbn/some-dev-log').SomeDevLog} log */ -export async function regenerateTsconfigPaths(tsconfigPaths, log) { +export async function regenerateTsconfigPaths(tsConfigRepoRels, log) { const path = Path.resolve(REPO_ROOT, 'packages/kbn-ts-projects/config-paths.json'); const existingContent = Fs.existsSync(path) ? await Fsp.readFile(path, 'utf8') : undefined; - const entries = [...tsconfigPaths] - .map((abs) => Path.relative(REPO_ROOT, abs)) - .sort((a, b) => a.localeCompare(b)); + const entries = Array.from(tsConfigRepoRels).sort((a, b) => a.localeCompare(b)); const content = JSON.stringify(entries, null, 2); if (content !== existingContent) { diff --git a/kbn_pm/src/commands/bootstrap/update_package_json.mjs b/kbn_pm/src/commands/bootstrap/update_package_json.mjs index 7b54a32a49631f..9886954009e355 100644 --- a/kbn_pm/src/commands/bootstrap/update_package_json.mjs +++ b/kbn_pm/src/commands/bootstrap/update_package_json.mjs @@ -64,7 +64,7 @@ export async function updatePackageJson(pkgs, log) { new Map(Object.entries(pkgJson.dependencies).filter(([k]) => k.startsWith('@kbn/'))), new Map( pkgs - .filter((p) => !p.isDevOnly) + .filter((p) => !p.isDevOnly()) .map((p) => [p.manifest.id, `link:${p.normalizedRepoRelativeDir}`]) ) ); @@ -74,7 +74,7 @@ export async function updatePackageJson(pkgs, log) { new Map(Object.entries(pkgJson.devDependencies).filter(([k]) => k.startsWith('@kbn/'))), new Map( pkgs - .filter((p) => p.isDevOnly) + .filter((p) => p.isDevOnly()) .map((p) => [p.manifest.id, `link:${p.normalizedRepoRelativeDir}`]) ) ); diff --git a/kbn_pm/src/commands/bootstrap/validate_package_json.mjs b/kbn_pm/src/commands/bootstrap/validate_package_json.mjs deleted file mode 100644 index 8a88898dcf3105..00000000000000 --- a/kbn_pm/src/commands/bootstrap/validate_package_json.mjs +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { createCliError } from '../../lib/cli_error.mjs'; - -/** - * @param {import('@kbn/repo-info').KibanaPackageJson} pkgJson - * @param {import('@kbn/some-dev-log').SomeDevLog} log - */ -export async function validatePackageJson(pkgJson, log) { - const failures = false; - - const typesInProd = Object.keys(pkgJson.dependencies).filter((id) => id.startsWith('@types/')); - if (typesInProd.length) { - const list = typesInProd.map((id) => ` - ${id}`).join('\n'); - log.error( - `The following @types/* packages are listed in dependencies but should be in the devDependencies:\n${list}` - ); - } - - if (failures) { - throw createCliError('failed to validate package.json, check for errors above'); - } -} diff --git a/kbn_pm/src/lib/external_packages.js b/kbn_pm/src/lib/external_packages.js index c50522bb82da34..6fdcd3c0951e9f 100644 --- a/kbn_pm/src/lib/external_packages.js +++ b/kbn_pm/src/lib/external_packages.js @@ -6,9 +6,11 @@ * Side Public License, v 1. */ module.exports = { + /** @returns {import('@kbn/repo-packages')} */ ['@kbn/repo-packages']() { - require('@kbn/babel-register').install(); - return require('@kbn/repo-packages'); + // we need to load this package before we install node modules so we can't use @kbn/* imports here + // eslint-disable-next-line import/no-dynamic-require + return require('../../../' + 'packages/kbn-repo-packages'); }, ['@kbn/ci-stats-reporter']() { @@ -30,24 +32,4 @@ module.exports = { require('@kbn/babel-register').install(); return require('@kbn/get-repo-files'); }, - - ['@kbn/repo-info']() { - require('@kbn/babel-register').install(); - return require('@kbn/repo-info'); - }, - - ['@kbn/ts-projects']() { - require('@kbn/babel-register').install(); - return require('@kbn/ts-projects'); - }, - - /** - * @param {string} absPath - * @returns {unknown} - */ - reqAbs(absPath) { - require('@kbn/babel-register').install(); - // eslint-disable-next-line import/no-dynamic-require - return require(absPath); - }, }; diff --git a/kbn_pm/tsconfig.json b/kbn_pm/tsconfig.json index 78c739c82b0606..8680fa52a45e7a 100644 --- a/kbn_pm/tsconfig.json +++ b/kbn_pm/tsconfig.json @@ -21,7 +21,8 @@ "@kbn/sort-package-json", { "path": "../src/dev/tsconfig.json" }, "@kbn/ci-stats-reporter", - "@kbn/ts-projects", - "@kbn/repo-packages" + "@kbn/repo-packages", + "@kbn/some-dev-log", + "@kbn/bazel-runner" ] } diff --git a/package.json b/package.json index 71e3570a2492db..05b4595bb850e0 100644 --- a/package.json +++ b/package.json @@ -120,24 +120,57 @@ "@hapi/hoek": "^9.2.1", "@hapi/inert": "^6.0.4", "@hapi/wreck": "^17.1.0", + "@kbn/aad-fixtures-plugin": "link:x-pack/test/alerting_api_integration/common/plugins/aad", "@kbn/ace": "link:packages/kbn-ace", + "@kbn/actions-plugin": "link:x-pack/plugins/actions", + "@kbn/actions-simulators-plugin": "link:x-pack/test/alerting_api_integration/common/plugins/actions_simulators", + "@kbn/advanced-settings-plugin": "link:src/plugins/advanced_settings", "@kbn/aiops-components": "link:x-pack/packages/ml/aiops_components", + "@kbn/aiops-plugin": "link:x-pack/plugins/aiops", "@kbn/aiops-utils": "link:x-pack/packages/ml/aiops_utils", + "@kbn/alerting-api-integration-test-plugin": "link:x-pack/test/alerting_api_integration/common/plugins/alerts", + "@kbn/alerting-example-plugin": "link:x-pack/examples/alerting_example", + "@kbn/alerting-fixture-plugin": "link:x-pack/test/functional_with_es_ssl/plugins/alerts", + "@kbn/alerting-plugin": "link:x-pack/plugins/alerting", "@kbn/alerts": "link:packages/kbn-alerts", + "@kbn/alerts-restricted-fixtures-plugin": "link:x-pack/test/alerting_api_integration/common/plugins/alerts_restricted", "@kbn/alerts-ui-shared": "link:packages/kbn-alerts-ui-shared", "@kbn/analytics": "link:packages/kbn-analytics", "@kbn/analytics-client": "link:packages/analytics/client", + "@kbn/analytics-ftr-helpers-plugin": "link:test/analytics/plugins/analytics_ftr_helpers", + "@kbn/analytics-plugin-a-plugin": "link:test/analytics/plugins/analytics_plugin_a", "@kbn/analytics-shippers-elastic-v3-browser": "link:packages/analytics/shippers/elastic_v3/browser", "@kbn/analytics-shippers-elastic-v3-common": "link:packages/analytics/shippers/elastic_v3/common", "@kbn/analytics-shippers-elastic-v3-server": "link:packages/analytics/shippers/elastic_v3/server", "@kbn/analytics-shippers-fullstory": "link:packages/analytics/shippers/fullstory", "@kbn/analytics-shippers-gainsight": "link:packages/analytics/shippers/gainsight", "@kbn/apm-config-loader": "link:packages/kbn-apm-config-loader", + "@kbn/apm-plugin": "link:x-pack/plugins/apm", "@kbn/apm-utils": "link:packages/kbn-apm-utils", + "@kbn/app-link-test-plugin": "link:test/plugin_functional/plugins/app_link_test", + "@kbn/application-usage-test-plugin": "link:x-pack/test/usage_collection/plugins/application_usage_test", + "@kbn/audit-log-plugin": "link:x-pack/test/security_api_integration/plugins/audit_log", + "@kbn/banners-plugin": "link:x-pack/plugins/banners", + "@kbn/bfetch-explorer-plugin": "link:examples/bfetch_explorer", + "@kbn/bfetch-plugin": "link:src/plugins/bfetch", + "@kbn/canvas-plugin": "link:x-pack/plugins/canvas", + "@kbn/cases-api-integration-test-plugin": "link:x-pack/test/cases_api_integration/common/plugins/cases", "@kbn/cases-components": "link:packages/kbn-cases-components", + "@kbn/cases-plugin": "link:x-pack/plugins/cases", "@kbn/cell-actions": "link:packages/kbn-cell-actions", "@kbn/chart-expressions-common": "link:src/plugins/chart_expressions/common", "@kbn/chart-icons": "link:packages/kbn-chart-icons", + "@kbn/charts-plugin": "link:src/plugins/charts", + "@kbn/cloud-chat-plugin": "link:x-pack/plugins/cloud_integrations/cloud_chat", + "@kbn/cloud-data-migration-plugin": "link:x-pack/plugins/cloud_integrations/cloud_data_migration", + "@kbn/cloud-defend-plugin": "link:x-pack/plugins/cloud_defend", + "@kbn/cloud-experiments-plugin": "link:x-pack/plugins/cloud_integrations/cloud_experiments", + "@kbn/cloud-full-story-plugin": "link:x-pack/plugins/cloud_integrations/cloud_full_story", + "@kbn/cloud-gainsight-plugin": "link:x-pack/plugins/cloud_integrations/cloud_gain_sight", + "@kbn/cloud-integration-saml-provider-plugin": "link:x-pack/test/cloud_integration/plugins/saml_provider", + "@kbn/cloud-links-plugin": "link:x-pack/plugins/cloud_integrations/cloud_links", + "@kbn/cloud-plugin": "link:x-pack/plugins/cloud", + "@kbn/cloud-security-posture-plugin": "link:x-pack/plugins/cloud_security_posture", "@kbn/code-editor": "link:packages/shared-ux/code_editor/impl", "@kbn/code-editor-mocks": "link:packages/shared-ux/code_editor/mocks", "@kbn/code-editor-types": "link:packages/shared-ux/code_editor/types", @@ -145,12 +178,18 @@ "@kbn/config": "link:packages/kbn-config", "@kbn/config-mocks": "link:packages/kbn-config-mocks", "@kbn/config-schema": "link:packages/kbn-config-schema", + "@kbn/console-plugin": "link:src/plugins/console", "@kbn/content-management-content-editor": "link:packages/content-management/content_editor", + "@kbn/content-management-plugin": "link:src/plugins/content_management", "@kbn/content-management-table-list": "link:packages/content-management/table_list", + "@kbn/controls-example-plugin": "link:examples/controls_example", + "@kbn/controls-plugin": "link:src/plugins/controls", + "@kbn/core": "link:src/core", "@kbn/core-analytics-browser": "link:packages/core/analytics/core-analytics-browser", "@kbn/core-analytics-browser-internal": "link:packages/core/analytics/core-analytics-browser-internal", "@kbn/core-analytics-server": "link:packages/core/analytics/core-analytics-server", "@kbn/core-analytics-server-internal": "link:packages/core/analytics/core-analytics-server-internal", + "@kbn/core-app-status-plugin": "link:test/plugin_functional/plugins/core_app_status", "@kbn/core-application-browser": "link:packages/core/application/core-application-browser", "@kbn/core-application-browser-internal": "link:packages/core/application/core-application-browser-internal", "@kbn/core-application-common": "link:packages/core/application/core-application-common", @@ -194,10 +233,12 @@ "@kbn/core-execution-context-server-internal": "link:packages/core/execution-context/core-execution-context-server-internal", "@kbn/core-fatal-errors-browser": "link:packages/core/fatal-errors/core-fatal-errors-browser", "@kbn/core-fatal-errors-browser-internal": "link:packages/core/fatal-errors/core-fatal-errors-browser-internal", + "@kbn/core-history-block-plugin": "link:test/plugin_functional/plugins/core_history_block", "@kbn/core-http-browser": "link:packages/core/http/core-http-browser", "@kbn/core-http-browser-internal": "link:packages/core/http/core-http-browser-internal", "@kbn/core-http-common": "link:packages/core/http/core-http-common", "@kbn/core-http-context-server-internal": "link:packages/core/http/core-http-context-server-internal", + "@kbn/core-http-plugin": "link:test/plugin_functional/plugins/core_http", "@kbn/core-http-request-handler-context-server": "link:packages/core/http/core-http-request-handler-context-server", "@kbn/core-http-request-handler-context-server-internal": "link:packages/core/http/core-http-request-handler-context-server-internal", "@kbn/core-http-resources-server": "link:packages/core/http/core-http-resources-server", @@ -231,6 +272,17 @@ "@kbn/core-notifications-browser-internal": "link:packages/core/notifications/core-notifications-browser-internal", "@kbn/core-overlays-browser": "link:packages/core/overlays/core-overlays-browser", "@kbn/core-overlays-browser-internal": "link:packages/core/overlays/core-overlays-browser-internal", + "@kbn/core-plugin-a-plugin": "link:test/plugin_functional/plugins/core_plugin_a", + "@kbn/core-plugin-appleave-plugin": "link:test/plugin_functional/plugins/core_plugin_appleave", + "@kbn/core-plugin-b-plugin": "link:test/plugin_functional/plugins/core_plugin_b", + "@kbn/core-plugin-chromeless-plugin": "link:test/plugin_functional/plugins/core_plugin_chromeless", + "@kbn/core-plugin-deep-links-plugin": "link:test/plugin_functional/plugins/core_plugin_deep_links", + "@kbn/core-plugin-deprecations-plugin": "link:test/plugin_functional/plugins/core_plugin_deprecations", + "@kbn/core-plugin-execution-context-plugin": "link:test/plugin_functional/plugins/core_plugin_execution_context", + "@kbn/core-plugin-helpmenu-plugin": "link:test/plugin_functional/plugins/core_plugin_helpmenu", + "@kbn/core-plugin-initializer-context-plugin": "link:test/node_roles_functional/plugins/core_plugin_initializer_context", + "@kbn/core-plugin-route-timeouts-plugin": "link:test/plugin_functional/plugins/core_plugin_route_timeouts", + "@kbn/core-plugin-static-assets-plugin": "link:test/plugin_functional/plugins/core_plugin_static_assets", "@kbn/core-plugins-base-server-internal": "link:packages/core/plugins/core-plugins-base-server-internal", "@kbn/core-plugins-browser": "link:packages/core/plugins/core-plugins-browser", "@kbn/core-plugins-browser-internal": "link:packages/core/plugins/core-plugins-browser-internal", @@ -238,6 +290,7 @@ "@kbn/core-plugins-server-internal": "link:packages/core/plugins/core-plugins-server-internal", "@kbn/core-preboot-server": "link:packages/core/preboot/core-preboot-server", "@kbn/core-preboot-server-internal": "link:packages/core/preboot/core-preboot-server-internal", + "@kbn/core-provider-plugin": "link:test/plugin_functional/plugins/core_provider_plugin", "@kbn/core-rendering-browser-internal": "link:packages/core/rendering/core-rendering-browser-internal", "@kbn/core-rendering-server-internal": "link:packages/core/rendering/core-rendering-server-internal", "@kbn/core-root-browser-internal": "link:packages/core/root/core-root-browser-internal", @@ -272,51 +325,207 @@ "@kbn/core-usage-data-base-server-internal": "link:packages/core/usage-data/core-usage-data-base-server-internal", "@kbn/core-usage-data-server": "link:packages/core/usage-data/core-usage-data-server", "@kbn/core-usage-data-server-internal": "link:packages/core/usage-data/core-usage-data-server-internal", + "@kbn/cross-cluster-replication-plugin": "link:x-pack/plugins/cross_cluster_replication", "@kbn/crypto": "link:packages/kbn-crypto", "@kbn/crypto-browser": "link:packages/kbn-crypto-browser", + "@kbn/custom-branding-plugin": "link:x-pack/plugins/custom_branding", + "@kbn/custom-integrations-plugin": "link:src/plugins/custom_integrations", + "@kbn/dashboard-embeddable-examples-plugin": "link:examples/dashboard_embeddable_examples", + "@kbn/dashboard-enhanced-plugin": "link:x-pack/plugins/dashboard_enhanced", + "@kbn/dashboard-plugin": "link:src/plugins/dashboard", + "@kbn/data-plugin": "link:src/plugins/data", + "@kbn/data-search-plugin": "link:test/plugin_functional/plugins/data_search", + "@kbn/data-view-editor-plugin": "link:src/plugins/data_view_editor", + "@kbn/data-view-field-editor-example-plugin": "link:examples/data_view_field_editor_example", + "@kbn/data-view-field-editor-plugin": "link:src/plugins/data_view_field_editor", + "@kbn/data-view-management-plugin": "link:src/plugins/data_view_management", + "@kbn/data-views-plugin": "link:src/plugins/data_views", + "@kbn/data-visualizer-plugin": "link:x-pack/plugins/data_visualizer", "@kbn/datemath": "link:packages/kbn-datemath", + "@kbn/dev-tools-plugin": "link:src/plugins/dev_tools", + "@kbn/developer-examples-plugin": "link:examples/developer_examples", + "@kbn/discover-enhanced-plugin": "link:x-pack/plugins/discover_enhanced", + "@kbn/discover-plugin": "link:src/plugins/discover", "@kbn/doc-links": "link:packages/kbn-doc-links", "@kbn/ebt-tools": "link:packages/kbn-ebt-tools", "@kbn/ecs": "link:packages/kbn-ecs", "@kbn/ecs-data-quality-dashboard": "link:x-pack/packages/kbn-ecs-data-quality-dashboard", + "@kbn/ecs-data-quality-dashboard-plugin": "link:x-pack/plugins/ecs_data_quality_dashboard", + "@kbn/elasticsearch-client-plugin": "link:test/plugin_functional/plugins/elasticsearch_client_plugin", + "@kbn/elasticsearch-client-xpack-plugin": "link:x-pack/test/plugin_api_integration/plugins/elasticsearch_client", + "@kbn/embeddable-enhanced-plugin": "link:x-pack/plugins/embeddable_enhanced", + "@kbn/embeddable-examples-plugin": "link:examples/embeddable_examples", + "@kbn/embeddable-explorer-plugin": "link:examples/embeddable_explorer", + "@kbn/embeddable-plugin": "link:src/plugins/embeddable", + "@kbn/embedded-lens-example-plugin": "link:x-pack/examples/embedded_lens_example", + "@kbn/encrypted-saved-objects-plugin": "link:x-pack/plugins/encrypted_saved_objects", + "@kbn/enterprise-search-plugin": "link:x-pack/plugins/enterprise_search", "@kbn/es-errors": "link:packages/kbn-es-errors", "@kbn/es-query": "link:packages/kbn-es-query", "@kbn/es-types": "link:packages/kbn-es-types", + "@kbn/es-ui-shared-plugin": "link:src/plugins/es_ui_shared", + "@kbn/eso-plugin": "link:x-pack/test/encrypted_saved_objects_api_integration/plugins/api_consumer_plugin", + "@kbn/event-annotation-plugin": "link:src/plugins/event_annotation", + "@kbn/event-log-fixture-plugin": "link:x-pack/test/plugin_api_integration/plugins/event_log", + "@kbn/event-log-plugin": "link:x-pack/plugins/event_log", + "@kbn/exploratory-view-example-plugin": "link:x-pack/examples/exploratory_view_example", + "@kbn/expression-error-plugin": "link:src/plugins/expression_error", + "@kbn/expression-gauge-plugin": "link:src/plugins/chart_expressions/expression_gauge", + "@kbn/expression-heatmap-plugin": "link:src/plugins/chart_expressions/expression_heatmap", + "@kbn/expression-image-plugin": "link:src/plugins/expression_image", + "@kbn/expression-legacy-metric-vis-plugin": "link:src/plugins/chart_expressions/expression_legacy_metric", + "@kbn/expression-metric-plugin": "link:src/plugins/expression_metric", + "@kbn/expression-metric-vis-plugin": "link:src/plugins/chart_expressions/expression_metric", + "@kbn/expression-partition-vis-plugin": "link:src/plugins/chart_expressions/expression_partition_vis", + "@kbn/expression-repeat-image-plugin": "link:src/plugins/expression_repeat_image", + "@kbn/expression-reveal-image-plugin": "link:src/plugins/expression_reveal_image", + "@kbn/expression-shape-plugin": "link:src/plugins/expression_shape", + "@kbn/expression-tagcloud-plugin": "link:src/plugins/chart_expressions/expression_tagcloud", + "@kbn/expression-xy-plugin": "link:src/plugins/chart_expressions/expression_xy", + "@kbn/expressions-explorer-plugin": "link:examples/expressions_explorer", + "@kbn/expressions-plugin": "link:src/plugins/expressions", + "@kbn/feature-usage-test-plugin": "link:x-pack/test/plugin_api_integration/plugins/feature_usage_test", + "@kbn/features-plugin": "link:x-pack/plugins/features", + "@kbn/fec-alerts-test-plugin": "link:x-pack/test/functional_execution_context/plugins/alerts", + "@kbn/field-formats-example-plugin": "link:examples/field_formats_example", + "@kbn/field-formats-plugin": "link:src/plugins/field_formats", "@kbn/field-types": "link:packages/kbn-field-types", + "@kbn/file-upload-plugin": "link:x-pack/plugins/file_upload", + "@kbn/files-example-plugin": "link:examples/files_example", + "@kbn/files-management-plugin": "link:src/plugins/files_management", + "@kbn/files-plugin": "link:src/plugins/files", + "@kbn/fleet-plugin": "link:x-pack/plugins/fleet", "@kbn/flot-charts": "link:packages/kbn-flot-charts", + "@kbn/foo-plugin": "link:x-pack/test/ui_capabilities/common/plugins/foo_plugin", + "@kbn/ftr-apis-plugin": "link:src/plugins/ftr_apis", + "@kbn/functional-with-es-ssl-cases-test-plugin": "link:x-pack/test/functional_with_es_ssl/plugins/cases", + "@kbn/global-search-bar-plugin": "link:x-pack/plugins/global_search_bar", + "@kbn/global-search-plugin": "link:x-pack/plugins/global_search", + "@kbn/global-search-providers-plugin": "link:x-pack/plugins/global_search_providers", + "@kbn/global-search-test-plugin": "link:x-pack/test/plugin_functional/plugins/global_search_test", + "@kbn/graph-plugin": "link:x-pack/plugins/graph", + "@kbn/grokdebugger-plugin": "link:x-pack/plugins/grokdebugger", "@kbn/guided-onboarding": "link:packages/kbn-guided-onboarding", + "@kbn/guided-onboarding-example-plugin": "link:examples/guided_onboarding_example", + "@kbn/guided-onboarding-plugin": "link:src/plugins/guided_onboarding", "@kbn/handlebars": "link:packages/kbn-handlebars", "@kbn/hapi-mocks": "link:packages/kbn-hapi-mocks", "@kbn/health-gateway-server": "link:packages/kbn-health-gateway-server", + "@kbn/hello-world-plugin": "link:examples/hello_world", + "@kbn/home-plugin": "link:src/plugins/home", "@kbn/home-sample-data-card": "link:packages/home/sample_data_card", "@kbn/home-sample-data-tab": "link:packages/home/sample_data_tab", "@kbn/home-sample-data-types": "link:packages/home/sample_data_types", "@kbn/i18n": "link:packages/kbn-i18n", "@kbn/i18n-react": "link:packages/kbn-i18n-react", + "@kbn/iframe-embedded-plugin": "link:x-pack/test/functional_embedded/plugins/iframe_embedded", + "@kbn/image-embeddable-plugin": "link:src/plugins/image_embeddable", + "@kbn/index-lifecycle-management-plugin": "link:x-pack/plugins/index_lifecycle_management", + "@kbn/index-management-plugin": "link:x-pack/plugins/index_management", + "@kbn/index-patterns-test-plugin": "link:test/plugin_functional/plugins/index_patterns", + "@kbn/infra-plugin": "link:x-pack/plugins/infra", + "@kbn/ingest-pipelines-plugin": "link:x-pack/plugins/ingest_pipelines", + "@kbn/input-control-vis-plugin": "link:src/plugins/input_control_vis", + "@kbn/inspector-plugin": "link:src/plugins/inspector", + "@kbn/interactive-setup-plugin": "link:src/plugins/interactive_setup", + "@kbn/interactive-setup-test-endpoints-plugin": "link:test/interactive_setup_api_integration/plugins/test_endpoints", "@kbn/interpreter": "link:packages/kbn-interpreter", "@kbn/io-ts-utils": "link:packages/kbn-io-ts-utils", + "@kbn/kbn-health-gateway-status-plugin": "link:test/health_gateway/plugins/status", + "@kbn/kbn-sample-panel-action-plugin": "link:test/plugin_functional/plugins/kbn_sample_panel_action", + "@kbn/kbn-top-nav-plugin": "link:test/plugin_functional/plugins/kbn_top_nav", + "@kbn/kbn-tp-custom-visualizations-plugin": "link:test/plugin_functional/plugins/kbn_tp_custom_visualizations", + "@kbn/kbn-tp-run-pipeline-plugin": "link:test/interpreter_functional/plugins/kbn_tp_run_pipeline", + "@kbn/kibana-cors-test-plugin": "link:x-pack/test/functional_cors/plugins/kibana_cors_test", + "@kbn/kibana-overview-plugin": "link:src/plugins/kibana_overview", + "@kbn/kibana-react-plugin": "link:src/plugins/kibana_react", + "@kbn/kibana-usage-collection-plugin": "link:src/plugins/kibana_usage_collection", + "@kbn/kibana-utils-plugin": "link:src/plugins/kibana_utils", + "@kbn/kubernetes-security-plugin": "link:x-pack/plugins/kubernetes_security", "@kbn/language-documentation-popover": "link:packages/kbn-language-documentation-popover", + "@kbn/lens-plugin": "link:x-pack/plugins/lens", + "@kbn/license-api-guard-plugin": "link:x-pack/plugins/license_api_guard", + "@kbn/license-management-plugin": "link:x-pack/plugins/license_management", + "@kbn/licensing-plugin": "link:x-pack/plugins/licensing", + "@kbn/lists-plugin": "link:x-pack/plugins/lists", + "@kbn/locator-examples-plugin": "link:examples/locator_examples", + "@kbn/locator-explorer-plugin": "link:examples/locator_explorer", "@kbn/logging": "link:packages/kbn-logging", "@kbn/logging-mocks": "link:packages/kbn-logging-mocks", + "@kbn/logstash-plugin": "link:x-pack/plugins/logstash", + "@kbn/management-plugin": "link:src/plugins/management", + "@kbn/management-test-plugin": "link:test/plugin_functional/plugins/management_test_plugin", "@kbn/mapbox-gl": "link:packages/kbn-mapbox-gl", + "@kbn/maps-custom-raster-source-plugin": "link:x-pack/examples/third_party_maps_source_example", + "@kbn/maps-ems-plugin": "link:src/plugins/maps_ems", + "@kbn/maps-plugin": "link:x-pack/plugins/maps", "@kbn/ml-agg-utils": "link:x-pack/packages/ml/agg_utils", "@kbn/ml-date-picker": "link:x-pack/packages/ml/date_picker", "@kbn/ml-is-defined": "link:x-pack/packages/ml/is_defined", "@kbn/ml-is-populated-object": "link:x-pack/packages/ml/is_populated_object", "@kbn/ml-local-storage": "link:x-pack/packages/ml/local_storage", "@kbn/ml-nested-property": "link:x-pack/packages/ml/nested_property", + "@kbn/ml-plugin": "link:x-pack/plugins/ml", "@kbn/ml-query-utils": "link:x-pack/packages/ml/query_utils", "@kbn/ml-string-hash": "link:x-pack/packages/ml/string_hash", "@kbn/ml-url-state": "link:x-pack/packages/ml/url_state", "@kbn/monaco": "link:packages/kbn-monaco", + "@kbn/monitoring-collection-plugin": "link:x-pack/plugins/monitoring_collection", + "@kbn/monitoring-plugin": "link:x-pack/plugins/monitoring", + "@kbn/navigation-plugin": "link:src/plugins/navigation", + "@kbn/newsfeed-plugin": "link:src/plugins/newsfeed", + "@kbn/newsfeed-test-plugin": "link:test/common/plugins/newsfeed", + "@kbn/notifications-plugin": "link:x-pack/plugins/notifications", + "@kbn/observability-fixtures-plugin": "link:x-pack/test/cases_api_integration/common/plugins/observability", + "@kbn/observability-plugin": "link:x-pack/plugins/observability", + "@kbn/oidc-provider-plugin": "link:x-pack/test/security_api_integration/plugins/oidc_provider", + "@kbn/open-telemetry-instrumented-plugin": "link:test/common/plugins/otel_metrics", "@kbn/osquery-io-ts-types": "link:packages/kbn-osquery-io-ts-types", - "@kbn/plugin-discovery": "link:packages/kbn-plugin-discovery", + "@kbn/osquery-plugin": "link:x-pack/plugins/osquery", + "@kbn/paertial-results-example-plugin": "link:examples/partial_results_example", + "@kbn/painless-lab-plugin": "link:x-pack/plugins/painless_lab", + "@kbn/preboot-example-plugin": "link:examples/preboot_example", + "@kbn/presentation-util-plugin": "link:src/plugins/presentation_util", + "@kbn/profiling-plugin": "link:x-pack/plugins/profiling", "@kbn/react-field": "link:packages/kbn-react-field", + "@kbn/remote-clusters-plugin": "link:x-pack/plugins/remote_clusters", + "@kbn/rendering-plugin": "link:test/plugin_functional/plugins/rendering_plugin", "@kbn/repo-info": "link:packages/kbn-repo-info", "@kbn/repo-packages": "link:packages/kbn-repo-packages", + "@kbn/reporting-example-plugin": "link:x-pack/examples/reporting_example", + "@kbn/reporting-plugin": "link:x-pack/plugins/reporting", + "@kbn/resolver-test-plugin": "link:x-pack/test/plugin_functional/plugins/resolver_test", + "@kbn/response-stream-plugin": "link:examples/response_stream", "@kbn/rison": "link:packages/kbn-rison", + "@kbn/rollup-plugin": "link:x-pack/plugins/rollup", + "@kbn/routing-example-plugin": "link:examples/routing_example", "@kbn/rule-data-utils": "link:packages/kbn-rule-data-utils", + "@kbn/rule-registry-plugin": "link:x-pack/plugins/rule_registry", + "@kbn/runtime-fields-plugin": "link:x-pack/plugins/runtime_fields", "@kbn/safer-lodash-set": "link:packages/kbn-safer-lodash-set", + "@kbn/saml-provider-plugin": "link:x-pack/test/security_api_integration/plugins/saml_provider", + "@kbn/sample-task-plugin": "link:x-pack/test/plugin_api_integration/plugins/sample_task_plugin", + "@kbn/saved-object-export-transforms-plugin": "link:test/plugin_functional/plugins/saved_object_export_transforms", + "@kbn/saved-object-import-warnings-plugin": "link:test/plugin_functional/plugins/saved_object_import_warnings", + "@kbn/saved-object-test-plugin": "link:x-pack/test/saved_object_api_integration/common/plugins/saved_object_test_plugin", + "@kbn/saved-objects-finder-plugin": "link:src/plugins/saved_objects_finder", + "@kbn/saved-objects-hidden-from-http-apis-type-plugin": "link:test/plugin_functional/plugins/saved_objects_hidden_from_http_apis_type", + "@kbn/saved-objects-hidden-type-plugin": "link:test/plugin_functional/plugins/saved_objects_hidden_type", + "@kbn/saved-objects-management-plugin": "link:src/plugins/saved_objects_management", + "@kbn/saved-objects-plugin": "link:src/plugins/saved_objects", + "@kbn/saved-objects-tagging-oss-plugin": "link:src/plugins/saved_objects_tagging_oss", + "@kbn/saved-objects-tagging-plugin": "link:x-pack/plugins/saved_objects_tagging", + "@kbn/saved-search-plugin": "link:src/plugins/saved_search", + "@kbn/screenshot-mode-example-plugin": "link:examples/screenshot_mode_example", + "@kbn/screenshot-mode-plugin": "link:src/plugins/screenshot_mode", + "@kbn/screenshotting-example-plugin": "link:x-pack/examples/screenshotting_example", + "@kbn/screenshotting-plugin": "link:x-pack/plugins/screenshotting", + "@kbn/search-examples-plugin": "link:examples/search_examples", + "@kbn/searchprofiler-plugin": "link:x-pack/plugins/searchprofiler", + "@kbn/security-plugin": "link:x-pack/plugins/security", + "@kbn/security-solution-fixtures-plugin": "link:x-pack/test/cases_api_integration/common/plugins/security_solution", + "@kbn/security-solution-plugin": "link:x-pack/plugins/security_solution", + "@kbn/security-test-endpoints-plugin": "link:x-pack/test/security_functional/plugins/test_endpoints", "@kbn/securitysolution-autocomplete": "link:packages/kbn-securitysolution-autocomplete", "@kbn/securitysolution-ecs": "link:packages/kbn-securitysolution-ecs", "@kbn/securitysolution-es-utils": "link:packages/kbn-securitysolution-es-utils", @@ -335,7 +544,11 @@ "@kbn/securitysolution-utils": "link:packages/kbn-securitysolution-utils", "@kbn/server-http-tools": "link:packages/kbn-server-http-tools", "@kbn/server-route-repository": "link:packages/kbn-server-route-repository", + "@kbn/session-notifications-plugin": "link:test/plugin_functional/plugins/session_notifications", + "@kbn/session-view-plugin": "link:x-pack/plugins/session_view", "@kbn/set-map": "link:packages/kbn-set-map", + "@kbn/share-examples-plugin": "link:examples/share_examples", + "@kbn/share-plugin": "link:src/plugins/share", "@kbn/shared-svg": "link:packages/kbn-shared-svg", "@kbn/shared-ux-avatar-solution": "link:packages/shared-ux/avatar/solution", "@kbn/shared-ux-avatar-user-profile-components": "link:packages/shared-ux/avatar/user_profile/impl", @@ -387,19 +600,79 @@ "@kbn/shared-ux-storybook-mock": "link:packages/shared-ux/storybook/mock", "@kbn/shared-ux-utility": "link:packages/kbn-shared-ux-utility", "@kbn/slo-schema": "link:packages/kbn-slo-schema", + "@kbn/snapshot-restore-plugin": "link:x-pack/plugins/snapshot_restore", + "@kbn/spaces-plugin": "link:x-pack/plugins/spaces", + "@kbn/spaces-test-plugin": "link:x-pack/test/spaces_api_integration/common/plugins/spaces_test_plugin", + "@kbn/stack-alerts-plugin": "link:x-pack/plugins/stack_alerts", + "@kbn/stack-connectors-plugin": "link:x-pack/plugins/stack_connectors", + "@kbn/stack-management-usage-test-plugin": "link:x-pack/test/usage_collection/plugins/stack_management_usage_test", + "@kbn/state-containers-examples-plugin": "link:examples/state_containers_examples", + "@kbn/status-plugin-a-plugin": "link:test/server_integration/plugins/status_plugin_a", + "@kbn/status-plugin-b-plugin": "link:test/server_integration/plugins/status_plugin_b", "@kbn/std": "link:packages/kbn-std", + "@kbn/synthetics-plugin": "link:x-pack/plugins/synthetics", + "@kbn/task-manager-fixture-plugin": "link:x-pack/test/alerting_api_integration/common/plugins/task_manager_fixture", + "@kbn/task-manager-performance-plugin": "link:x-pack/test/plugin_api_perf/plugins/task_manager_performance", + "@kbn/task-manager-plugin": "link:x-pack/plugins/task_manager", + "@kbn/telemetry-collection-manager-plugin": "link:src/plugins/telemetry_collection_manager", + "@kbn/telemetry-collection-xpack-plugin": "link:x-pack/plugins/telemetry_collection_xpack", + "@kbn/telemetry-management-section-plugin": "link:src/plugins/telemetry_management_section", + "@kbn/telemetry-plugin": "link:src/plugins/telemetry", + "@kbn/telemetry-test-plugin": "link:test/plugin_functional/plugins/telemetry", + "@kbn/test-feature-usage-plugin": "link:x-pack/test/licensing_plugin/plugins/test_feature_usage", + "@kbn/testing-embedded-lens-plugin": "link:x-pack/examples/testing_embedded_lens", + "@kbn/third-party-lens-navigation-prompt-plugin": "link:x-pack/examples/third_party_lens_navigation_prompt", + "@kbn/third-party-vis-lens-example-plugin": "link:x-pack/examples/third_party_vis_lens_example", + "@kbn/threat-intelligence-plugin": "link:x-pack/plugins/threat_intelligence", + "@kbn/timelines-plugin": "link:x-pack/plugins/timelines", "@kbn/timelion-grammar": "link:packages/kbn-timelion-grammar", "@kbn/tinymath": "link:packages/kbn-tinymath", + "@kbn/transform-plugin": "link:x-pack/plugins/transform", + "@kbn/translations-plugin": "link:x-pack/plugins/translations", + "@kbn/triggers-actions-ui-example-plugin": "link:x-pack/examples/triggers_actions_ui_example", + "@kbn/triggers-actions-ui-plugin": "link:x-pack/plugins/triggers_actions_ui", "@kbn/typed-react-router-config": "link:packages/kbn-typed-react-router-config", "@kbn/ui-actions-browser": "link:packages/kbn-ui-actions-browser", + "@kbn/ui-actions-enhanced-examples-plugin": "link:x-pack/examples/ui_actions_enhanced_examples", + "@kbn/ui-actions-enhanced-plugin": "link:src/plugins/ui_actions_enhanced", + "@kbn/ui-actions-examples-plugin": "link:examples/ui_action_examples", + "@kbn/ui-actions-explorer-plugin": "link:examples/ui_actions_explorer", + "@kbn/ui-actions-plugin": "link:src/plugins/ui_actions", "@kbn/ui-framework": "link:packages/kbn-ui-framework", + "@kbn/ui-settings-plugin": "link:test/plugin_functional/plugins/ui_settings_plugin", "@kbn/ui-shared-deps-npm": "link:packages/kbn-ui-shared-deps-npm", "@kbn/ui-shared-deps-src": "link:packages/kbn-ui-shared-deps-src", "@kbn/ui-theme": "link:packages/kbn-ui-theme", + "@kbn/unified-field-list-plugin": "link:src/plugins/unified_field_list", + "@kbn/unified-histogram-plugin": "link:src/plugins/unified_histogram", + "@kbn/unified-search-plugin": "link:src/plugins/unified_search", + "@kbn/upgrade-assistant-plugin": "link:x-pack/plugins/upgrade_assistant", + "@kbn/url-drilldown-plugin": "link:x-pack/plugins/drilldowns/url_drilldown", + "@kbn/url-forwarding-plugin": "link:src/plugins/url_forwarding", + "@kbn/usage-collection-plugin": "link:src/plugins/usage_collection", + "@kbn/usage-collection-test-plugin": "link:test/plugin_functional/plugins/usage_collection", "@kbn/user-profile-components": "link:packages/kbn-user-profile-components", + "@kbn/user-profile-examples-plugin": "link:examples/user_profile_examples", + "@kbn/user-profiles-consumer-plugin": "link:x-pack/test/security_api_integration/plugins/user_profiles_consumer", "@kbn/utility-types": "link:packages/kbn-utility-types", "@kbn/utility-types-jest": "link:packages/kbn-utility-types-jest", "@kbn/utils": "link:packages/kbn-utils", + "@kbn/ux-plugin": "link:x-pack/plugins/ux", + "@kbn/vis-default-editor-plugin": "link:src/plugins/vis_default_editor", + "@kbn/vis-type-gauge-plugin": "link:src/plugins/vis_types/gauge", + "@kbn/vis-type-heatmap-plugin": "link:src/plugins/vis_types/heatmap", + "@kbn/vis-type-markdown-plugin": "link:src/plugins/vis_type_markdown", + "@kbn/vis-type-metric-plugin": "link:src/plugins/vis_types/metric", + "@kbn/vis-type-pie-plugin": "link:src/plugins/vis_types/pie", + "@kbn/vis-type-table-plugin": "link:src/plugins/vis_types/table", + "@kbn/vis-type-tagcloud-plugin": "link:src/plugins/vis_types/tagcloud", + "@kbn/vis-type-timelion-plugin": "link:src/plugins/vis_types/timelion", + "@kbn/vis-type-timeseries-plugin": "link:src/plugins/vis_types/timeseries", + "@kbn/vis-type-vega-plugin": "link:src/plugins/vis_types/vega", + "@kbn/vis-type-vislib-plugin": "link:src/plugins/vis_types/vislib", + "@kbn/vis-type-xy-plugin": "link:src/plugins/vis_types/xy", + "@kbn/visualizations-plugin": "link:src/plugins/visualizations", + "@kbn/watcher-plugin": "link:x-pack/plugins/watcher", "@loaders.gl/core": "^2.3.1", "@loaders.gl/json": "^2.3.1", "@loaders.gl/shapefile": "^2.3.1", @@ -702,7 +975,6 @@ "@kbn/apm-synthtrace": "link:packages/kbn-apm-synthtrace", "@kbn/apm-synthtrace-client": "link:packages/kbn-apm-synthtrace-client", "@kbn/axe-config": "link:packages/kbn-axe-config", - "@kbn/babel-plugin-package-imports": "link:packages/kbn-babel-plugin-package-imports", "@kbn/babel-preset": "link:packages/kbn-babel-preset", "@kbn/babel-register": "link:packages/kbn-babel-register", "@kbn/babel-transform": "link:packages/kbn-babel-transform", @@ -808,6 +1080,7 @@ "@kbn/repo-path": "link:packages/kbn-repo-path", "@kbn/repo-source-classifier": "link:packages/kbn-repo-source-classifier", "@kbn/repo-source-classifier-cli": "link:packages/kbn-repo-source-classifier-cli", + "@kbn/security-api-integration-helpers": "link:x-pack/test/security_api_integration/packages/helpers", "@kbn/some-dev-log": "link:packages/kbn-some-dev-log", "@kbn/sort-package-json": "link:packages/kbn-sort-package-json", "@kbn/spec-to-console": "link:packages/kbn-spec-to-console", diff --git a/packages/core/apps/core-apps-server-internal/src/bundle_routes/register_bundle_routes.test.ts b/packages/core/apps/core-apps-server-internal/src/bundle_routes/register_bundle_routes.test.ts index 249d3880c07d62..0e1770eada9b1c 100644 --- a/packages/core/apps/core-apps-server-internal/src/bundle_routes/register_bundle_routes.test.ts +++ b/packages/core/apps/core-apps-server-internal/src/bundle_routes/register_bundle_routes.test.ts @@ -77,7 +77,7 @@ describe('registerBundleRoutes', () => { expect(registerRouteForBundleMock).toHaveBeenCalledWith(router, { fileHashCache: expect.any(FileHashCache), isDist: true, - bundlesPath: expect.stringMatching(/src\/core\/target\/public/), + bundlesPath: expect.stringMatching(/\/@kbn\/core\/target\/public$/), publicPath: '/server-base-path/42/bundles/core/', routePath: '/42/bundles/core/', }); diff --git a/packages/core/apps/core-apps-server-internal/src/bundle_routes/register_bundle_routes.ts b/packages/core/apps/core-apps-server-internal/src/bundle_routes/register_bundle_routes.ts index ad1008c5ac1e38..22266e97355e37 100644 --- a/packages/core/apps/core-apps-server-internal/src/bundle_routes/register_bundle_routes.ts +++ b/packages/core/apps/core-apps-server-internal/src/bundle_routes/register_bundle_routes.ts @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -import { join } from 'path'; import type { PackageInfo } from '@kbn/config'; import { fromRoot } from '@kbn/repo-info'; import UiSharedDepsNpm from '@kbn/ui-shared-deps-npm'; @@ -61,7 +60,9 @@ export function registerBundleRoutes({ registerRouteForBundle(router, { publicPath: `${serverBasePath}/${buildNum}/bundles/core/`, routePath: `/${buildNum}/bundles/core/`, - bundlesPath: fromRoot(join('src', 'core', 'target', 'public')), + bundlesPath: isDist + ? fromRoot('node_modules/@kbn/core/target/public') + : fromRoot('src/core/target/public'), fileHashCache, isDist, }); diff --git a/packages/core/i18n/core-i18n-server-internal/src/get_kibana_translation_files.test.ts b/packages/core/i18n/core-i18n-server-internal/src/get_kibana_translation_files.test.ts index 76c8ba7c02808c..cca3eef6ffaffe 100644 --- a/packages/core/i18n/core-i18n-server-internal/src/get_kibana_translation_files.test.ts +++ b/packages/core/i18n/core-i18n-server-internal/src/get_kibana_translation_files.test.ts @@ -17,8 +17,15 @@ jest.mock('./get_translation_paths', () => ({ jest.mock('@kbn/repo-info', () => ({ fromRoot: jest.fn().mockImplementation((path: string) => path), })); +jest.mock('@kbn/repo-packages', () => { + return { + getPackages: jest.fn().mockReturnValue([]), + getPluginPackagesFilter: jest.fn().mockImplementation(() => () => false), + }; +}); const locale = 'en'; +const { getPackages, getPluginPackagesFilter } = jest.requireMock('@kbn/repo-packages'); describe('getKibanaTranslationPaths', () => { beforeEach(() => { @@ -67,4 +74,21 @@ describe('getKibanaTranslationPaths', () => { expect(translationFiles).toEqual(['/root/en.json', '/kibana-extra/en.json']); }); + + it('looks for translation paths in filters plugin packages', async () => { + const package1 = { directory: 'package1' }; + const package2 = { directory: 'package2' }; + const filter = jest.fn((p: any) => p === package2); + + getPackages.mockReturnValue([package1, package2]); + getPluginPackagesFilter.mockReturnValue(filter); + + await getKibanaTranslationFiles(locale, []); + expect(getPackages).toHaveBeenCalledTimes(1); + expect(getPluginPackagesFilter).toHaveBeenCalledTimes(1); + expect(filter).toHaveBeenCalledTimes(2); + expect(mockGetTranslationPaths).toHaveBeenCalledTimes(3); + expect(mockGetTranslationPaths).not.toHaveBeenCalledWith({ cwd: 'package1', nested: false }); + expect(mockGetTranslationPaths).toHaveBeenCalledWith({ cwd: 'package2', nested: false }); + }); }); diff --git a/packages/core/i18n/core-i18n-server-internal/src/get_kibana_translation_files.ts b/packages/core/i18n/core-i18n-server-internal/src/get_kibana_translation_files.ts index 672ec2f0a3a278..7928f273431039 100644 --- a/packages/core/i18n/core-i18n-server-internal/src/get_kibana_translation_files.ts +++ b/packages/core/i18n/core-i18n-server-internal/src/get_kibana_translation_files.ts @@ -8,6 +8,8 @@ import { basename } from 'path'; import { fromRoot } from '@kbn/repo-info'; +import { asyncMapWithLimit } from '@kbn/std'; +import { getPackages, getPluginPackagesFilter } from '@kbn/repo-packages'; import { getTranslationPaths } from './get_translation_paths'; export const getKibanaTranslationFiles = async ( @@ -19,14 +21,23 @@ export const getKibanaTranslationFiles = async ( cwd: fromRoot('.'), nested: true, }), - ...pluginPaths.map((pluginPath) => getTranslationPaths({ cwd: pluginPath, nested: false })), + asyncMapWithLimit( + getPackages(fromRoot('.')).filter(getPluginPackagesFilter({ paths: pluginPaths })), + 20, + async (pkg) => await getTranslationPaths({ cwd: pkg.directory, nested: false }) + ), + asyncMapWithLimit( + pluginPaths, + 20, + async (pluginPath) => await getTranslationPaths({ cwd: pluginPath, nested: false }) + ), getTranslationPaths({ cwd: fromRoot('../kibana-extra'), nested: true, }), ]); - return ([] as string[]) - .concat(...translationPaths) + return translationPaths + .flat(2) .filter((translationPath) => basename(translationPath, '.json') === locale); }; diff --git a/packages/core/i18n/core-i18n-server-internal/src/get_translation_paths.ts b/packages/core/i18n/core-i18n-server-internal/src/get_translation_paths.ts index c0479208ba87e8..9cc055d2bd8be5 100644 --- a/packages/core/i18n/core-i18n-server-internal/src/get_translation_paths.ts +++ b/packages/core/i18n/core-i18n-server-internal/src/get_translation_paths.ts @@ -26,12 +26,12 @@ export async function getTranslationPaths({ cwd, nested }: { cwd: string; nested const pluginBasePath = dirname(entryFullPath); try { const content = await readFile(entryFullPath, 'utf8'); - const { translations } = JSON.parse(content) as I18NRCFileStructure; - if (translations && translations.length) { - translations.forEach((translation) => { - const translationFullPath = resolve(pluginBasePath, translation); - translationPaths.push(translationFullPath); - }); + const { translations = [] } = JSON.parse(content) as I18NRCFileStructure; + + for (const path of translations) { + translationPaths.push( + path.startsWith('@kbn/') ? require.resolve(path) : resolve(pluginBasePath, path) + ); } } catch (err) { throw new Error(`Failed to parse .i18nrc.json file at ${entryFullPath}`); diff --git a/packages/core/i18n/core-i18n-server-internal/tsconfig.json b/packages/core/i18n/core-i18n-server-internal/tsconfig.json index 813e3469b746c6..ea139de89eb196 100644 --- a/packages/core/i18n/core-i18n-server-internal/tsconfig.json +++ b/packages/core/i18n/core-i18n-server-internal/tsconfig.json @@ -23,6 +23,8 @@ "@kbn/core-base-server-mocks", "@kbn/core-http-server-mocks", "@kbn/i18n", + "@kbn/std", + "@kbn/repo-packages", ], "exclude": [ "target/**/*", diff --git a/packages/core/plugins/core-plugins-server-internal/src/discovery/plugins_discovery.test.ts b/packages/core/plugins/core-plugins-server-internal/src/discovery/plugins_discovery.test.ts index 0a2daf42690d2d..48a872e3d67c3c 100644 --- a/packages/core/plugins/core-plugins-server-internal/src/discovery/plugins_discovery.test.ts +++ b/packages/core/plugins/core-plugins-server-internal/src/discovery/plugins_discovery.test.ts @@ -25,10 +25,8 @@ import type { InstanceInfo } from '../plugin_context'; import { discover } from './plugins_discovery'; import { PluginType } from '@kbn/core-base-common'; -const KIBANA_ROOT = process.cwd(); jest.mock('@kbn/repo-packages', () => ({ ...jest.requireActual('@kbn/repo-packages'), - getPackages: jest.fn().mockReturnValue([]), getPluginPackagesFilter: jest.fn().mockReturnValue(() => true), })); @@ -153,8 +151,8 @@ const packageMock = { }, }; -const manifestPath = (...pluginPath: string[]) => - resolve(KIBANA_ROOT, 'src', 'plugins', ...pluginPath, 'kibana.json'); +const pluginDir = (...segments: string[]) => resolve(REPO_ROOT, 'plugins', ...segments); +const manifestPath = (...pluginPath: string[]) => resolve(pluginDir(...pluginPath), 'kibana.json'); describe('plugins discovery system', () => { let logger: ReturnType; @@ -185,6 +183,7 @@ describe('plugins discovery system', () => { REPO_ROOT, getEnvOptions({ cliArgs: { envName: 'development' }, + repoPackages: [], }) ); @@ -230,10 +229,10 @@ describe('plugins discovery system', () => { mockFs( { - [`${KIBANA_ROOT}/src/plugins/plugin_a`]: Plugins.valid('pluginA'), - [`${KIBANA_ROOT}/plugins/plugin_b`]: Plugins.valid('pluginB'), - [`${KIBANA_ROOT}/x-pack/plugins/plugin_c`]: Plugins.valid('pluginC'), - [`${KIBANA_ROOT}/src/plugins/plugin_d`]: Plugins.validPreboot('pluginD'), + [pluginDir('plugin_a')]: Plugins.valid('pluginA'), + [pluginDir('plugin_b')]: Plugins.valid('pluginB'), + [pluginDir(`plugin_c`)]: Plugins.valid('pluginC'), + [pluginDir(`plugin_d`)]: Plugins.validPreboot('pluginD'), }, { createCwd: false } ); @@ -257,12 +256,12 @@ describe('plugins discovery system', () => { mockFs( { - [`${KIBANA_ROOT}/src/plugins/plugin_a`]: Plugins.invalid(), - [`${KIBANA_ROOT}/src/plugins/plugin_b`]: Plugins.incomplete(), - [`${KIBANA_ROOT}/src/plugins/plugin_c`]: Plugins.incompatible(), - [`${KIBANA_ROOT}/src/plugins/plugin_d`]: Plugins.incompatibleType('pluginD'), - [`${KIBANA_ROOT}/src/plugins/plugin_ad`]: Plugins.missingManifest(), - [`${KIBANA_ROOT}/src/plugins/plugin_e`]: Plugins.missingOwnerAttribute(), + [pluginDir(`plugin_a`)]: Plugins.invalid(), + [pluginDir(`plugin_b`)]: Plugins.incomplete(), + [pluginDir(`plugin_c`)]: Plugins.incompatible(), + [pluginDir(`plugin_d`)]: Plugins.incompatibleType('pluginD'), + [pluginDir(`plugin_ad`)]: Plugins.missingManifest(), + [pluginDir(`plugin_e`)]: Plugins.missingOwnerAttribute(), }, { createCwd: false } ); @@ -324,7 +323,7 @@ describe('plugins discovery system', () => { mockFs( { - [`${KIBANA_ROOT}/src/plugins`]: mockFs.directory({ + [pluginDir('.')]: mockFs.directory({ mode: 0, // 0000 items: { plugin_a: Plugins.valid('pluginA'), @@ -344,12 +343,38 @@ describe('plugins discovery system', () => { ) .toPromise(); - const srcPluginsPath = resolve(KIBANA_ROOT, 'src', 'plugins'); - const xpackPluginsPath = resolve(KIBANA_ROOT, 'x-pack', 'plugins'); + const srcPluginsPath = pluginDir('.'); expect(errors).toEqual( expect.arrayContaining([ `Error: EACCES, permission denied '${srcPluginsPath}' (invalid-search-path, ${srcPluginsPath})`, - `Error: ENOENT, no such file or directory '${xpackPluginsPath}' (invalid-search-path, ${xpackPluginsPath})`, + ]) + ); + }); + + it('return errors when the plugin search path is missing', async () => { + const { plugin$, error$ } = discover({ + config: new PluginsConfig(pluginConfig, env), + coreContext, + instanceInfo, + nodeInfo, + }); + + mockFs({}, { createCwd: false }); + + const plugins = await plugin$.pipe(toArray()).toPromise(); + expect(plugins).toHaveLength(0); + + const errors = await error$ + .pipe( + map((error) => error.toString()), + toArray() + ) + .toPromise(); + + const srcPluginsPath = pluginDir('.'); + expect(errors).toEqual( + expect.arrayContaining([ + `Error: ENOENT, no such file or directory '${srcPluginsPath}' (invalid-search-path, ${srcPluginsPath})`, ]) ); }); @@ -364,7 +389,7 @@ describe('plugins discovery system', () => { mockFs( { - [`${KIBANA_ROOT}/src/plugins/plugin_a`]: { + [pluginDir(`plugin_a`)]: { ...Plugins.inaccessibleManifest(), nested_plugin: Plugins.valid('nestedPlugin'), }, @@ -400,11 +425,11 @@ describe('plugins discovery system', () => { mockFs( { - [`${KIBANA_ROOT}/src/plugins/plugin_a`]: Plugins.valid('pluginA'), - [`${KIBANA_ROOT}/src/plugins/sub1/plugin_b`]: Plugins.valid('pluginB'), - [`${KIBANA_ROOT}/src/plugins/sub1/sub2/plugin_c`]: Plugins.valid('pluginC'), - [`${KIBANA_ROOT}/src/plugins/sub1/sub2/plugin_d`]: Plugins.validPreboot('pluginD'), - [`${KIBANA_ROOT}/src/plugins/sub1/sub2/plugin_e`]: Plugins.incomplete(), + [pluginDir(`plugin_a`)]: Plugins.valid('pluginA'), + [pluginDir(`sub1/plugin_b`)]: Plugins.valid('pluginB'), + [pluginDir(`sub1/sub2/plugin_c`)]: Plugins.valid('pluginC'), + [pluginDir(`sub1/sub2/plugin_d`)]: Plugins.validPreboot('pluginD'), + [pluginDir(`sub1/sub2/plugin_e`)]: Plugins.incomplete(), }, { createCwd: false } ); @@ -445,7 +470,7 @@ describe('plugins discovery system', () => { mockFs( { - [`${KIBANA_ROOT}/src/plugins/plugin_a`]: { + [pluginDir(`plugin_a`)]: { ...Plugins.valid('pluginA'), nested_plugin: Plugins.valid('nestedPlugin'), }, @@ -469,13 +494,12 @@ describe('plugins discovery system', () => { mockFs( { - [`${KIBANA_ROOT}/src/plugins/sub1/plugin`]: Plugins.valid('plugin1'), - [`${KIBANA_ROOT}/src/plugins/sub1/sub2/plugin`]: Plugins.valid('plugin2'), - [`${KIBANA_ROOT}/src/plugins/sub1/sub2/sub3/plugin`]: Plugins.valid('plugin3'), - [`${KIBANA_ROOT}/src/plugins/sub1/sub2/sub3/sub4/plugin`]: Plugins.valid('plugin4'), - [`${KIBANA_ROOT}/src/plugins/sub1/sub2/sub3/sub4/sub5/plugin`]: Plugins.valid('plugin5'), - [`${KIBANA_ROOT}/src/plugins/sub1/sub2/sub3/sub4/sub5/sub6/plugin`]: - Plugins.valid('plugin6'), + [pluginDir(`sub1/plugin`)]: Plugins.valid('plugin1'), + [pluginDir(`sub1/sub2/plugin`)]: Plugins.valid('plugin2'), + [pluginDir(`sub1/sub2/sub3/plugin`)]: Plugins.valid('plugin3'), + [pluginDir(`sub1/sub2/sub3/sub4/plugin`)]: Plugins.valid('plugin4'), + [pluginDir(`sub1/sub2/sub3/sub4/sub5/plugin`)]: Plugins.valid('plugin5'), + [pluginDir(`sub1/sub2/sub3/sub4/sub5/sub6/plugin`)]: Plugins.valid('plugin6'), }, { createCwd: false } ); @@ -497,12 +521,12 @@ describe('plugins discovery system', () => { nodeInfo, }); - const pluginFolder = resolve(KIBANA_ROOT, '..', 'ext-plugins'); + const pluginFolder = pluginDir('../ext-plugins'); mockFs( { - [`${KIBANA_ROOT}/plugins`]: mockFs.symlink({ - path: '../ext-plugins', + [pluginDir(`.`)]: mockFs.symlink({ + path: pluginFolder, }), [pluginFolder]: { plugin_a: Plugins.valid('pluginA'), @@ -634,18 +658,18 @@ describe('plugins discovery system', () => { it('returns the plugins in a deterministic order', async () => { mockFs( { - [`${KIBANA_ROOT}/src/plugins/plugin_a`]: Plugins.valid('pluginA'), - [`${KIBANA_ROOT}/plugins/plugin_b`]: Plugins.valid('pluginB'), - [`${KIBANA_ROOT}/x-pack/plugins/plugin_c`]: Plugins.valid('pluginC'), + [`${REPO_ROOT}/src/plugins/plugin_a`]: Plugins.valid('pluginA'), + [`${REPO_ROOT}/plugins/plugin_b`]: Plugins.valid('pluginB'), + [`${REPO_ROOT}/x-pack/plugins/plugin_c`]: Plugins.valid('pluginC'), }, { createCwd: false } ); scanPluginSearchPathsMock.mockReturnValue( from([ - `${KIBANA_ROOT}/src/plugins/plugin_a`, - `${KIBANA_ROOT}/plugins/plugin_b`, - `${KIBANA_ROOT}/x-pack/plugins/plugin_c`, + `${REPO_ROOT}/src/plugins/plugin_a`, + `${REPO_ROOT}/plugins/plugin_b`, + `${REPO_ROOT}/x-pack/plugins/plugin_c`, ]) ); @@ -671,9 +695,9 @@ describe('plugins discovery system', () => { // second pass scanPluginSearchPathsMock.mockReturnValue( from([ - `${KIBANA_ROOT}/plugins/plugin_b`, - `${KIBANA_ROOT}/x-pack/plugins/plugin_c`, - `${KIBANA_ROOT}/src/plugins/plugin_a`, + `${REPO_ROOT}/plugins/plugin_b`, + `${REPO_ROOT}/x-pack/plugins/plugin_c`, + `${REPO_ROOT}/src/plugins/plugin_a`, ]) ); diff --git a/packages/core/plugins/core-plugins-server-internal/src/plugins_service.test.ts b/packages/core/plugins/core-plugins-server-internal/src/plugins_service.test.ts index 948939b143dd05..62742499471cc9 100644 --- a/packages/core/plugins/core-plugins-server-internal/src/plugins_service.test.ts +++ b/packages/core/plugins/core-plugins-server-internal/src/plugins_service.test.ts @@ -722,10 +722,8 @@ describe('PluginsService', () => { additionalPluginPaths: [], initialize: true, pluginSearchPaths: [ - resolve(process.cwd(), 'src', 'plugins'), - resolve(process.cwd(), 'x-pack', 'plugins'), - resolve(process.cwd(), 'plugins'), - resolve(process.cwd(), '..', 'kibana-extra'), + resolve(REPO_ROOT, '..', 'kibana-extra'), + resolve(REPO_ROOT, 'plugins'), ], }, coreContext: { coreId, env, logger, configService }, diff --git a/packages/kbn-apm-synthtrace-client/package.json b/packages/kbn-apm-synthtrace-client/package.json index 24d780a8ba4aad..060ed0631da494 100644 --- a/packages/kbn-apm-synthtrace-client/package.json +++ b/packages/kbn-apm-synthtrace-client/package.json @@ -1,6 +1,6 @@ { "name": "@kbn/apm-synthtrace-client", - "version": "0.1.0", + "version": "1.0.0", "description": "Elastic APM trace data generator", "license": "SSPL-1.0 OR Elastic License 2.0", "private": true diff --git a/packages/kbn-apm-synthtrace/package.json b/packages/kbn-apm-synthtrace/package.json index 827e251d8a1372..426dd4fcddbe10 100644 --- a/packages/kbn-apm-synthtrace/package.json +++ b/packages/kbn-apm-synthtrace/package.json @@ -1,6 +1,6 @@ { "name": "@kbn/apm-synthtrace", - "version": "0.1.0", + "version": "1.0.0", "description": "Elastic APM trace data generator", "license": "SSPL-1.0 OR Elastic License 2.0", "bin": { diff --git a/packages/kbn-babel-plugin-package-imports/BUILD.bazel b/packages/kbn-babel-plugin-package-imports/BUILD.bazel deleted file mode 100644 index 74e10c2a943ff4..00000000000000 --- a/packages/kbn-babel-plugin-package-imports/BUILD.bazel +++ /dev/null @@ -1,31 +0,0 @@ -load("@build_bazel_rules_nodejs//:index.bzl", "js_library") - -SRCS = [ - "babel_plugin_package_imports.js", - "index.js", -] - -# In this array place runtime dependencies, including other packages and NPM packages -# which must be available for this code to run. -# -# To reference other packages use: -# "//repo/relative/path/to/package" -# eg. "//packages/kbn-utils" -# -# To reference a NPM package use: -# "@npm//name-of-package" -# eg. "@npm//lodash" -BUNDLER_DEPS = [ - "@npm//@babel/helper-plugin-utils", - "@npm//normalize-path", - "//packages/kbn-repo-info", - "//packages/kbn-repo-packages", -] - -js_library( - name = "kbn-babel-plugin-package-imports", - package_name = "@kbn/babel-plugin-package-imports", - srcs = ["package.json"] + SRCS, - deps = BUNDLER_DEPS, - visibility = ["//visibility:public"], -) diff --git a/packages/kbn-babel-plugin-package-imports/README.mdx b/packages/kbn-babel-plugin-package-imports/README.mdx deleted file mode 100644 index 1f36b2266b4a95..00000000000000 --- a/packages/kbn-babel-plugin-package-imports/README.mdx +++ /dev/null @@ -1,11 +0,0 @@ ---- -id: kibDevDocsOpsBabelPluginPackageImports -slug: /kibana-dev-docs/ops/babel-plugin-package-imports -title: "@kbn/babel-plugin-package-imports" -description: A babel plugin that transforms our @kbn/{NAME} imports into paths -date: 2022-05-19 -tags: ['kibana', 'dev', 'contributor', 'operations', 'babel', 'plugin', 'packages', 'imports'] ---- - -When developing inside the Kibana repository importing a package from any other package is just easy as -importing `@kbn/{package-name}`. However not every package is a node_module yet and while that is something we are working on to accomplish we need a way to dealing with it for now. Using this babel plugin is our transitory solution. It allows us to import from module ids and then transform it automatically back into paths on the transpiled code without friction for our engineering teams. \ No newline at end of file diff --git a/packages/kbn-babel-plugin-package-imports/babel_plugin_package_imports.js b/packages/kbn-babel-plugin-package-imports/babel_plugin_package_imports.js deleted file mode 100644 index 04cd5874079ac3..00000000000000 --- a/packages/kbn-babel-plugin-package-imports/babel_plugin_package_imports.js +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -const Path = require('path'); - -const T = require('@babel/types'); -const normalizePath = require('normalize-path'); -const { declare } = require('@babel/helper-plugin-utils'); -const { readPackageMap } = require('@kbn/repo-packages'); -const { REPO_ROOT } = require('@kbn/repo-info'); - -const PKG_MAP = readPackageMap(); - -/** - * @param {unknown} v - * @returns {v is Record} - */ -const isObj = (v) => typeof v === 'object' && !!v; - -/** - * @param {unknown} state - * @returns {string} - */ -function getFilename(state) { - if (!isObj(state) || typeof state.filename !== 'string' || !Path.isAbsolute(state.filename)) { - throw new Error( - `@kbn/babel-plugin-package-imports is only compatible when building files with absolute filename state` - ); - } - - return state.filename; -} - -/** - * @param {string} req - * @returns {import('./types').ParsedReq | undefined} - */ -function parseReq(req) { - if (!req.startsWith('@kbn/')) { - return; - } - - const parts = req.split('/'); - const moduleId = `@kbn/${parts[1]}`; - const dir = PKG_MAP.get(moduleId); - if (!dir) { - return; - } - - return { - req, - moduleId, - dir, - subParts: parts.slice(2), - }; -} - -/** - * @param {import('./types').ParsedReq} req - * @param {string} filename - */ -function fixImportRequest(req, filename) { - if (process.env.BAZEL_WORKSPACE === 'kibana') { - return; - } - - const rel = normalizePath( - Path.relative(Path.dirname(filename), Path.resolve(REPO_ROOT, req.dir, ...req.subParts)) - ); - - return rel.startsWith('.') ? rel : `./${rel}`; -} - -/** - * @param {T.CallExpression} node - * @returns {node is T.Import & { arguments: [T.StringLiteral] }} - */ -function isDynamicImport(node) { - return !!( - T.isImport(node.callee) && - node.arguments.length === 1 && - T.isStringLiteral(node.arguments[0]) - ); -} - -/** - * @param {T.CallExpression} node - * @returns {node is T.CallExpression & { arguments: [T.StringLiteral] }} - */ -function isRequire(node) { - return !!( - T.isIdentifier(node.callee) && - node.callee.name === 'require' && - node.arguments.length >= 1 && - T.isStringLiteral(node.arguments[0]) - ); -} - -/** - * @param {T.CallExpression} node - * @returns {node is T.CallExpression & { arguments: [T.StringLiteral] }} - */ -function isRequireResolve(node) { - return !!( - T.isMemberExpression(node.callee) && - T.isIdentifier(node.callee.object) && - node.callee.object.name === 'require' && - T.isIdentifier(node.callee.property) && - node.callee.property.name === 'resolve' && - node.arguments.length >= 1 && - T.isStringLiteral(node.arguments[0]) - ); -} - -/** - * @param {T.CallExpression} node - * @returns {node is T.CallExpression & { arguments: [T.StringLiteral] }} - */ -function isJestMockCall(node) { - return !!( - T.isMemberExpression(node.callee) && - T.isIdentifier(node.callee.object) && - node.callee.object.name === 'jest' && - node.arguments.length >= 1 && - T.isStringLiteral(node.arguments[0]) - ); -} - -module.exports = declare((api, options) => { - /** @type {Set | undefined} */ - const ignoredPkgIds = options.ignoredPkgIds; - - api.assertVersion(7); - - return { - name: 'kbn-package-imports', - visitor: { - /** - * @param {import('@babel/core').NodePath} path - */ - 'ImportDeclaration|ExportNamedDeclaration|ExportAllDeclaration'(path) { - const filename = getFilename(this); - - const source = path.node.source; - if (!T.isStringLiteral(source)) { - return; - } - - const req = source.value; - const parsed = parseReq(req); - if (!parsed || ignoredPkgIds?.has(parsed.moduleId)) { - return; - } - - const newReq = fixImportRequest(parsed, filename); - if (newReq) { - path.get('source').replaceWith(T.stringLiteral(newReq)); - } - }, - - /** - * @param {import('@babel/core').NodePath} path - */ - CallExpression(path) { - const filename = getFilename(this); - - const { node } = path; - if ( - !isDynamicImport(node) && - !isRequire(node) && - !isRequireResolve(node) && - !isJestMockCall(node) - ) { - return; - } - - const req = node.arguments[0].value; - const parsed = parseReq(req); - if (!parsed || ignoredPkgIds?.has(parsed.moduleId)) { - return; - } - - const newReq = fixImportRequest(parsed, filename); - if (newReq) { - path.get('arguments')[0].replaceWith(T.stringLiteral(newReq)); - } - }, - }, - }; -}); diff --git a/packages/kbn-babel-plugin-package-imports/index.js b/packages/kbn-babel-plugin-package-imports/index.js deleted file mode 100644 index ce314829e93c39..00000000000000 --- a/packages/kbn-babel-plugin-package-imports/index.js +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -module.exports = require('./babel_plugin_package_imports'); diff --git a/packages/kbn-babel-plugin-package-imports/kibana.jsonc b/packages/kbn-babel-plugin-package-imports/kibana.jsonc deleted file mode 100644 index c555d9f0087761..00000000000000 --- a/packages/kbn-babel-plugin-package-imports/kibana.jsonc +++ /dev/null @@ -1,6 +0,0 @@ -{ - "type": "shared-common", - "id": "@kbn/babel-plugin-package-imports", - "devOnly": true, - "owner": "@elastic/kibana-operations" -} diff --git a/packages/kbn-babel-plugin-package-imports/package.json b/packages/kbn-babel-plugin-package-imports/package.json deleted file mode 100644 index 1d7206c6b10014..00000000000000 --- a/packages/kbn-babel-plugin-package-imports/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "@kbn/babel-plugin-package-imports", - "private": true, - "version": "1.0.0", - "license": "SSPL-1.0 OR Elastic License 2.0" -} diff --git a/packages/kbn-babel-plugin-package-imports/types.ts b/packages/kbn-babel-plugin-package-imports/types.ts deleted file mode 100644 index c556aa9b073392..00000000000000 --- a/packages/kbn-babel-plugin-package-imports/types.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export interface ParsedReq { - req: string; - moduleId: string; - dir: string; - subParts: string[]; -} diff --git a/packages/kbn-babel-preset/BUILD.bazel b/packages/kbn-babel-preset/BUILD.bazel index 73313161f6ac4e..a8ae67bfea870d 100644 --- a/packages/kbn-babel-preset/BUILD.bazel +++ b/packages/kbn-babel-preset/BUILD.bazel @@ -39,7 +39,6 @@ RUNTIME_DEPS = [ "@npm//babel-plugin-add-module-exports", "@npm//babel-plugin-styled-components", "@npm//babel-plugin-transform-react-remove-prop-types", - "//packages/kbn-babel-plugin-package-imports", ] js_library( diff --git a/packages/kbn-babel-preset/common_preset.js b/packages/kbn-babel-preset/common_preset.js index cfb8bf495c6a23..f8ce4c63537972 100644 --- a/packages/kbn-babel-preset/common_preset.js +++ b/packages/kbn-babel-preset/common_preset.js @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -module.exports = (_, options = {}) => ({ +module.exports = () => ({ presets: [ // plugins always run before presets, but in this case we need the // @babel/preset-typescript preset to run first so we have to move @@ -46,19 +46,6 @@ module.exports = (_, options = {}) => ({ version: '^7.12.5', }, ], - - ...(options['kibana/ignoreAllPkgImports'] - ? [] - : [ - [ - require.resolve('@kbn/babel-plugin-package-imports'), - { - ignoredPkgIds: options['kibana/ignoredPkgIds'] - ? new Set(options['kibana/ignoredPkgIds']) - : undefined, - }, - ], - ]), ], }, diff --git a/packages/kbn-babel-transform/options.js b/packages/kbn-babel-transform/options.js index 4b98a790414e71..ed0decb9f0da29 100644 --- a/packages/kbn-babel-transform/options.js +++ b/packages/kbn-babel-transform/options.js @@ -21,14 +21,7 @@ const cwd = process.cwd(); function getBabelOptions(path, config = {}) { return { filename: path, - presets: [ - [ - NODE_PRESET, - { - 'kibana/ignoredPkgIds': config.ignoredPkgIds, - }, - ], - ], + presets: [NODE_PRESET], cwd, babelrc: false, sourceMaps: config.disableSourceMaps ? false : 'both', diff --git a/packages/kbn-babel-transform/types.ts b/packages/kbn-babel-transform/types.ts index 1ccb31d9ebb7f2..5267d76935da08 100644 --- a/packages/kbn-babel-transform/types.ts +++ b/packages/kbn-babel-transform/types.ts @@ -8,7 +8,6 @@ export interface TransformConfig { disableSourceMaps?: boolean; - ignoredPkgIds?: string[]; } export interface WorkerData { diff --git a/packages/kbn-cli-dev-mode/src/cli_dev_mode.test.ts b/packages/kbn-cli-dev-mode/src/cli_dev_mode.test.ts index 89a849150bcf40..769242436a270d 100644 --- a/packages/kbn-cli-dev-mode/src/cli_dev_mode.test.ts +++ b/packages/kbn-cli-dev-mode/src/cli_dev_mode.test.ts @@ -116,7 +116,6 @@ it('passes correct args to sub-classes', () => { "cache": true, "dist": true, "enabled": true, - "oss": true, "pluginPaths": Array [], "pluginScanDirs": Array [ /src/plugins, diff --git a/packages/kbn-cli-dev-mode/src/cli_dev_mode.ts b/packages/kbn-cli-dev-mode/src/cli_dev_mode.ts index 2dc1470e71b49f..5dc2fb9985c319 100644 --- a/packages/kbn-cli-dev-mode/src/cli_dev_mode.ts +++ b/packages/kbn-cli-dev-mode/src/cli_dev_mode.ts @@ -149,9 +149,6 @@ export class CliDevMode { this.optimizer = new Optimizer({ enabled: !cliArgs.disableOptimizer, repoRoot: REPO_ROOT, - oss: cliArgs.oss, - pluginPaths: config.plugins.additionalPluginPaths, - pluginScanDirs: config.plugins.pluginSearchPaths, runExamples: cliArgs.runExamples, cache: cliArgs.cache, dist: cliArgs.dist, @@ -159,6 +156,8 @@ export class CliDevMode { silent: !!cliArgs.silent, verbose: !!cliArgs.verbose, watch: cliArgs.watch, + pluginPaths: config.plugins.additionalPluginPaths, + pluginScanDirs: config.plugins.pluginSearchPaths, }); } diff --git a/packages/kbn-cli-dev-mode/src/optimizer.test.ts b/packages/kbn-cli-dev-mode/src/optimizer.test.ts index 3cb847c9594e36..b4d7fe6c6dfdb7 100644 --- a/packages/kbn-cli-dev-mode/src/optimizer.test.ts +++ b/packages/kbn-cli-dev-mode/src/optimizer.test.ts @@ -43,7 +43,6 @@ const defaultOptions: Options = { enabled: true, cache: true, dist: true, - oss: true, pluginPaths: ['/some/dir'], pluginScanDirs: ['/some-scan-path'], quiet: true, @@ -85,7 +84,6 @@ it('uses options to create valid OptimizerConfig', () => { cache: false, dist: false, runExamples: false, - oss: false, pluginPaths: [], pluginScanDirs: [], repoRoot: '/foo/bar', @@ -100,7 +98,6 @@ it('uses options to create valid OptimizerConfig', () => { "dist": true, "examples": true, "includeCoreBundle": true, - "oss": true, "pluginPaths": Array [ "/some/dir", ], @@ -117,7 +114,6 @@ it('uses options to create valid OptimizerConfig', () => { "dist": false, "examples": false, "includeCoreBundle": true, - "oss": false, "pluginPaths": Array [], "pluginScanDirs": Array [], "repoRoot": "/foo/bar", diff --git a/packages/kbn-cli-dev-mode/src/optimizer.ts b/packages/kbn-cli-dev-mode/src/optimizer.ts index 60d84d2b42c85b..50f2425f842930 100644 --- a/packages/kbn-cli-dev-mode/src/optimizer.ts +++ b/packages/kbn-cli-dev-mode/src/optimizer.ts @@ -35,11 +35,10 @@ export interface Options { watch: boolean; cache: boolean; dist: boolean; - oss: boolean; runExamples: boolean; - pluginPaths: string[]; - pluginScanDirs: string[]; writeLogTo?: Writable; + pluginPaths?: string[]; + pluginScanDirs?: string[]; } export class Optimizer { @@ -61,7 +60,6 @@ export class Optimizer { includeCoreBundle: true, cache: options.cache, dist: options.dist, - oss: options.oss, examples: options.runExamples, pluginPaths: options.pluginPaths, pluginScanDirs: options.pluginScanDirs, diff --git a/packages/kbn-cli-dev-mode/src/watcher.ts b/packages/kbn-cli-dev-mode/src/watcher.ts index e4ade054b473d8..cdcc5db8f65bb5 100644 --- a/packages/kbn-cli-dev-mode/src/watcher.ts +++ b/packages/kbn-cli-dev-mode/src/watcher.ts @@ -15,19 +15,17 @@ import { makeMatcher } from '@kbn/picomatcher'; import { Log } from './log'; -const packageMatcher = makeMatcher(['**/*', '!**/.*']); +const packageMatcher = makeMatcher([ + '**/*', + '!**/.*', + '!x-pack/plugins/screenshotting/chromium/**', + '!x-pack/plugins/canvas/shareable_runtime/**', +]); /** * Any code that is outside of a package must match this in order to trigger a restart */ -const nonPackageMatcher = makeMatcher([ - 'config/**/*.yml', - 'src/**', - '!src/{dev,fixtures}/**', - 'x-pack/plugins/**', - '!x-pack/plugins/screenshotting/chromium/**', - '!x-pack/plugins/canvas/shareable_runtime/**', -]); +const nonPackageMatcher = makeMatcher(['config/**/*.yml']); export interface Options { enabled: boolean; @@ -84,6 +82,7 @@ export class Watcher { if (result.type === 'common package' || result.type === 'server package') { return packageMatcher(result.repoRel) && fire(result.repoRel); } + if (result.type === 'non-package') { return nonPackageMatcher(result.repoRel) && fire(result.repoRel); } diff --git a/packages/kbn-config-mocks/src/env.mock.ts b/packages/kbn-config-mocks/src/env.mock.ts index 8ed2123a3c81a4..dfc01c171a9eff 100644 --- a/packages/kbn-config-mocks/src/env.mock.ts +++ b/packages/kbn-config-mocks/src/env.mock.ts @@ -11,7 +11,11 @@ import { getPackages } from '@kbn/repo-packages'; import { Env, type RawPackageInfo, type EnvOptions } from '@kbn/config'; type DeepPartial = { - [P in keyof T]?: T[P] extends Array ? Array> : DeepPartial; + [P in keyof T]?: P extends 'repoPackages' + ? T[P] + : T[P] extends Array + ? Array> + : DeepPartial; }; export function getEnvOptions(options: DeepPartial = {}): EnvOptions { @@ -29,7 +33,7 @@ export function getEnvOptions(options: DeepPartial = {}): EnvOptions runExamples: false, ...(options.cliArgs || {}), }, - repoPackages: getPackages(REPO_ROOT), + repoPackages: options.repoPackages ?? getPackages(REPO_ROOT), }; } diff --git a/packages/kbn-config/src/__snapshots__/env.test.ts.snap b/packages/kbn-config/src/__snapshots__/env.test.ts.snap index e356e6b5af142d..3f2dde3c3f563e 100644 --- a/packages/kbn-config/src/__snapshots__/env.test.ts.snap +++ b/packages/kbn-config/src/__snapshots__/env.test.ts.snap @@ -34,10 +34,8 @@ Env { "version": "v1", }, "pluginSearchPaths": Array [ - "/test/kibanaRoot/src/plugins", - "/test/kibanaRoot/x-pack/plugins", + "/test/kibana-extra", "/test/kibanaRoot/plugins", - "/test/kibanaRoot/../kibana-extra", ], "repoPackages": undefined, } @@ -77,10 +75,8 @@ Env { "version": "v1", }, "pluginSearchPaths": Array [ - "/test/kibanaRoot/src/plugins", - "/test/kibanaRoot/x-pack/plugins", + "/test/kibana-extra", "/test/kibanaRoot/plugins", - "/test/kibanaRoot/../kibana-extra", ], "repoPackages": undefined, } @@ -119,10 +115,8 @@ Env { "version": "some-version", }, "pluginSearchPaths": Array [ - "/test/kibanaRoot/src/plugins", - "/test/kibanaRoot/x-pack/plugins", + "/test/kibana-extra", "/test/kibanaRoot/plugins", - "/test/kibanaRoot/../kibana-extra", ], "repoPackages": undefined, } @@ -161,10 +155,8 @@ Env { "version": "v1", }, "pluginSearchPaths": Array [ - "/test/kibanaRoot/src/plugins", - "/test/kibanaRoot/x-pack/plugins", + "/test/kibana-extra", "/test/kibanaRoot/plugins", - "/test/kibanaRoot/../kibana-extra", ], "repoPackages": undefined, } @@ -203,10 +195,8 @@ Env { "version": "v1", }, "pluginSearchPaths": Array [ - "/test/kibanaRoot/src/plugins", - "/test/kibanaRoot/x-pack/plugins", + "/test/kibana-extra", "/test/kibanaRoot/plugins", - "/test/kibanaRoot/../kibana-extra", ], "repoPackages": undefined, } @@ -245,10 +235,8 @@ Env { "version": "v1", }, "pluginSearchPaths": Array [ - "/some/home/dir/src/plugins", - "/some/home/dir/x-pack/plugins", + "/some/home/kibana-extra", "/some/home/dir/plugins", - "/some/home/dir/../kibana-extra", ], "repoPackages": Array [ "FakePackage1", diff --git a/packages/kbn-config/src/env.test.mocks.ts b/packages/kbn-config/src/env.test.mocks.ts index 19abcf74e9bbab..4e581a43ebc679 100644 --- a/packages/kbn-config/src/env.test.mocks.ts +++ b/packages/kbn-config/src/env.test.mocks.ts @@ -5,14 +5,10 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - const realPath = jest.requireActual('path'); jest.doMock('path', () => ({ ...realPath, - resolve(...pathSegments: string[]) { - return pathSegments.join('/'); - }, dirname(filePath: string) { return '/test/kibanaRoot'; }, diff --git a/packages/kbn-config/src/env.test.ts b/packages/kbn-config/src/env.test.ts index 8633654c14ca94..782b0cc5081c69 100644 --- a/packages/kbn-config/src/env.test.ts +++ b/packages/kbn-config/src/env.test.ts @@ -140,86 +140,56 @@ test('correctly creates environment with constructor.', () => { expect(env).toMatchSnapshot('env properties'); }); -test('pluginSearchPaths contains x-pack plugins path if --oss flag is false', () => { +test('pluginSearchPaths only includes kibana-extra, regardless of plugin filters', () => { const env = new Env( '/some/home/dir', packageInfos, getEnvOptions({ - cliArgs: { oss: false }, - }) - ); - - expect(env.pluginSearchPaths).toContain('/some/home/dir/x-pack/plugins'); -}); - -test('pluginSearchPaths does not contains x-pack plugins path if --oss flag is true', () => { - const env = new Env( - '/some/home/dir', - packageInfos, - getEnvOptions({ - cliArgs: { oss: true }, - }) - ); - - expect(env.pluginSearchPaths).not.toContain('/some/home/dir/x-pack/plugins'); -}); - -test('pluginSearchPaths contains examples plugins path if --run-examples flag is true', () => { - const env = new Env( - '/some/home/dir', - packageInfos, - getEnvOptions({ - cliArgs: { runExamples: true }, - }) - ); - - expect(env.pluginSearchPaths).toContain('/some/home/dir/examples'); -}); - -test('pluginSearchPaths contains x-pack/examples plugins path if --run-examples flag is true', () => { - const env = new Env( - '/some/home/dir', - packageInfos, - getEnvOptions({ - cliArgs: { runExamples: true }, + cliArgs: { + oss: false, + runExamples: false, + }, }) ); - expect(env.pluginSearchPaths).toContain('/some/home/dir/x-pack/examples'); -}); + expect(env.pluginSearchPaths).toEqual(['/some/home/kibana-extra', '/some/home/dir/plugins']); -test('pluginSearchPaths does not contain x-pack/examples plugins path if --oss flag is true', () => { - const env = new Env( + const env2 = new Env( '/some/home/dir', packageInfos, getEnvOptions({ - cliArgs: { runExamples: true, oss: true }, + cliArgs: { + oss: true, + runExamples: true, + }, }) ); - expect(env.pluginSearchPaths).not.toContain('/some/home/dir/x-pack/examples'); -}); + expect(env2.pluginSearchPaths).toEqual(['/some/home/kibana-extra', '/some/home/dir/plugins']); -test('pluginSearchPaths does not contains examples plugins path if --run-examples flag is false', () => { - const env = new Env( + const env3 = new Env( '/some/home/dir', packageInfos, getEnvOptions({ - cliArgs: { runExamples: false }, + cliArgs: { + oss: true, + runExamples: false, + }, }) ); - expect(env.pluginSearchPaths).not.toContain('/some/home/dir/examples'); -}); + expect(env3.pluginSearchPaths).toEqual(['/some/home/kibana-extra', '/some/home/dir/plugins']); -test('pluginSearchPaths does not contains x-pack/examples plugins path if --run-examples flag is false', () => { - const env = new Env( + const env4 = new Env( '/some/home/dir', packageInfos, getEnvOptions({ - cliArgs: { runExamples: false }, + cliArgs: { + oss: false, + runExamples: true, + }, }) ); - expect(env.pluginSearchPaths).not.toContain('/some/home/dir/x-pack/examples'); + expect(env4.pluginSearchPaths).toEqual(['/some/home/kibana-extra', '/some/home/dir/plugins']); }); diff --git a/packages/kbn-config/src/env.ts b/packages/kbn-config/src/env.ts index 416f409bfcc253..dde77b26fa86f4 100644 --- a/packages/kbn-config/src/env.ts +++ b/packages/kbn-config/src/env.ts @@ -8,7 +8,7 @@ import { resolve, join } from 'path'; import loadJsonFile from 'load-json-file'; -import { getPluginSearchPaths } from '@kbn/plugin-discovery'; +import { getPluginSearchPaths } from '@kbn/repo-packages'; import type { Package } from '@kbn/repo-packages'; import { PackageInfo, EnvironmentMode } from './types'; @@ -101,8 +101,6 @@ export class Env { this.pluginSearchPaths = getPluginSearchPaths({ rootDir: this.homeDir, - oss: options.cliArgs.oss, - examples: options.cliArgs.runExamples, }); this.repoPackages = options.repoPackages; diff --git a/packages/kbn-config/tsconfig.json b/packages/kbn-config/tsconfig.json index 887f6aa96e2278..e9978b43fa8c8d 100644 --- a/packages/kbn-config/tsconfig.json +++ b/packages/kbn-config/tsconfig.json @@ -18,7 +18,6 @@ "@kbn/std", "@kbn/utility-types", "@kbn/i18n", - "@kbn/plugin-discovery", "@kbn/doc-links", "@kbn/repo-packages" ], diff --git a/packages/kbn-datemath/package.json b/packages/kbn-datemath/package.json index e6c119d7a1f885..38422694d88fe8 100644 --- a/packages/kbn-datemath/package.json +++ b/packages/kbn-datemath/package.json @@ -1,8 +1,8 @@ { "name": "@kbn/datemath", - "version": "5.0.4", + "version": "1.0.0", "description": "elasticsearch datemath parser, used in kibana", - "license": "Apache-2.0", + "license": "SSPL-1.0 OR Elastic License 2.0", "peerDependencies": { "moment": "^2.24.0" } diff --git a/packages/kbn-dev-utils/src/plugin_list/discover_plugins.ts b/packages/kbn-dev-utils/src/plugin_list/discover_plugins.ts index f86d535e92914f..d6d6b75729fca5 100644 --- a/packages/kbn-dev-utils/src/plugin_list/discover_plugins.ts +++ b/packages/kbn-dev-utils/src/plugin_list/discover_plugins.ts @@ -12,7 +12,7 @@ import Fs from 'fs'; import MarkdownIt from 'markdown-it'; import cheerio from 'cheerio'; import { REPO_ROOT } from '@kbn/repo-info'; -import { simpleKibanaPlatformPluginDiscovery } from '@kbn/plugin-discovery'; +import { getPackages, getPluginPackagesFilter } from '@kbn/repo-packages'; import { extractAsciidocInfo } from './extract_asciidoc_info'; @@ -34,9 +34,12 @@ const getReadmeName = (directory: string) => const getReadmeAsciidocName = (directory: string) => Fs.readdirSync(directory).find((name) => name.toLowerCase() === 'readme.asciidoc'); -export const discoverPlugins = (pluginsRootDir: string): Plugins => - simpleKibanaPlatformPluginDiscovery([pluginsRootDir], []).map( - ({ directory, manifest: { id } }): Plugin => { +export const discoverPlugins = (pluginDir: string): Plugins => + getPackages(REPO_ROOT) + .filter(getPluginPackagesFilter()) + .filter((pkg) => pkg.normalizedRepoRelativeDir.startsWith(pluginDir + '/')) + .map((pkg): Plugin => { + const directory = Path.resolve(REPO_ROOT, pkg.normalizedRepoRelativeDir); const readmeName = getReadmeName(directory); const readmeAsciidocName = getReadmeAsciidocName(directory); @@ -68,11 +71,10 @@ export const discoverPlugins = (pluginsRootDir: string): Plugins => } return { - id, + id: pkg.manifest.plugin.id, relativeReadmePath, relativeDir: relativeReadmePath || Path.relative(REPO_ROOT, directory), readmeSnippet, readmeAsciidocAnchor, }; - } - ); + }); diff --git a/packages/kbn-dev-utils/src/plugin_list/run_plugin_list_cli.ts b/packages/kbn-dev-utils/src/plugin_list/run_plugin_list_cli.ts index 69bcd3389bf1c2..b981f62e988327 100644 --- a/packages/kbn-dev-utils/src/plugin_list/run_plugin_list_cli.ts +++ b/packages/kbn-dev-utils/src/plugin_list/run_plugin_list_cli.ts @@ -14,18 +14,16 @@ import { run } from '@kbn/dev-cli-runner'; import { discoverPlugins } from './discover_plugins'; import { generatePluginList } from './generate_plugin_list'; -const OSS_PLUGIN_DIR = Path.resolve(REPO_ROOT, 'src/plugins'); -const XPACK_PLUGIN_DIR = Path.resolve(REPO_ROOT, 'x-pack/plugins'); const OUTPUT_PATH = Path.resolve(REPO_ROOT, 'docs/developer/plugin-list.asciidoc'); export function runPluginListCli() { run(async ({ log }) => { log.info('looking for oss plugins'); - const ossPlugins = discoverPlugins(OSS_PLUGIN_DIR); + const ossPlugins = discoverPlugins('src/plugins'); log.success(`found ${ossPlugins.length} plugins`); log.info('looking for x-pack plugins'); - const xpackPlugins = discoverPlugins(XPACK_PLUGIN_DIR); + const xpackPlugins = discoverPlugins('x-pack/plugins'); log.success(`found ${xpackPlugins.length} plugins`); log.info('writing plugin list to', OUTPUT_PATH); diff --git a/packages/kbn-dev-utils/tsconfig.json b/packages/kbn-dev-utils/tsconfig.json index d66b40e46e31a7..5fd26e3590aba0 100644 --- a/packages/kbn-dev-utils/tsconfig.json +++ b/packages/kbn-dev-utils/tsconfig.json @@ -13,8 +13,8 @@ "kbn_references": [ "@kbn/dev-cli-runner", "@kbn/dev-cli-errors", - "@kbn/plugin-discovery", "@kbn/repo-info", + "@kbn/repo-packages", ], "exclude": [ "target/**/*", diff --git a/packages/kbn-docs-utils/src/build_api_declarations/buid_api_declaration.test.ts b/packages/kbn-docs-utils/src/build_api_declarations/buid_api_declaration.test.ts index 8cea39236ee861..f4f2cd013c65ae 100644 --- a/packages/kbn-docs-utils/src/build_api_declarations/buid_api_declaration.test.ts +++ b/packages/kbn-docs-utils/src/build_api_declarations/buid_api_declaration.test.ts @@ -48,7 +48,7 @@ it('Test number primitive doc def', () => { const def = buildApiDeclarationTopNode(node!, { plugins, log, - currentPluginId: plugins[0].manifest.id, + currentPluginId: plugins[0].id, scope: ApiScope.CLIENT, captureReferences: false, }); @@ -62,7 +62,7 @@ it('Test a constructor type declaration inside an interface', () => { const def = buildApiDeclarationTopNode(node!, { plugins, log, - currentPluginId: plugins[0].manifest.id, + currentPluginId: plugins[0].id, scope: ApiScope.CLIENT, captureReferences: false, }); @@ -80,7 +80,7 @@ it('Function type is exported as type with signature', () => { const def = buildApiDeclarationTopNode(node!, { plugins, log, - currentPluginId: plugins[0].manifest.id, + currentPluginId: plugins[0].id, scope: ApiScope.CLIENT, captureReferences: false, }); @@ -95,7 +95,7 @@ it('Test Interface Kind doc def', () => { const def = buildApiDeclarationTopNode(node!, { plugins, log, - currentPluginId: plugins[0].manifest.id, + currentPluginId: plugins[0].id, scope: ApiScope.CLIENT, captureReferences: false, }); @@ -111,7 +111,7 @@ it('Test union export', () => { const def = buildApiDeclarationTopNode(node!, { plugins, log, - currentPluginId: plugins[0].manifest.id, + currentPluginId: plugins[0].id, scope: ApiScope.CLIENT, captureReferences: false, }); @@ -124,7 +124,7 @@ it('Function inside interface has a label', () => { const def = buildApiDeclarationTopNode(node!, { plugins, log, - currentPluginId: plugins[0].manifest.id, + currentPluginId: plugins[0].id, scope: ApiScope.CLIENT, captureReferences: false, }); @@ -142,7 +142,7 @@ it.skip('Test ReactElement signature', () => { const def = buildApiDeclarationTopNode(node!, { plugins, log, - currentPluginId: plugins[0].manifest.id, + currentPluginId: plugins[0].id, scope: ApiScope.CLIENT, captureReferences: false, }); diff --git a/packages/kbn-docs-utils/src/build_api_declarations/extract_import_refs.test.ts b/packages/kbn-docs-utils/src/build_api_declarations/extract_import_refs.test.ts index 592d0591846b1f..df71dc09d66599 100644 --- a/packages/kbn-docs-utils/src/build_api_declarations/extract_import_refs.test.ts +++ b/packages/kbn-docs-utils/src/build_api_declarations/extract_import_refs.test.ts @@ -50,7 +50,7 @@ it('test extractImportReference', () => { it('test extractImportReference with a package', () => { const results = extractImportReferences( - `(param: string) => import("Users/foo/node_modules/${packageA.manifest.id}/target_types").Bar`, + `(param: string) => import("Users/foo/node_modules/${packageA.id}/target_types").Bar`, plugins, log ); @@ -58,9 +58,9 @@ it('test extractImportReference with a package', () => { expect(results[0]).toBe('(param: string) => '); expect(results[1]).toEqual({ text: 'Bar', - docId: getPluginApiDocId(packageA.manifest.id), + docId: getPluginApiDocId(packageA.id), section: 'def-common.Bar', - pluginId: packageA.manifest.id, + pluginId: packageA.id, scope: ApiScope.COMMON, }); }); diff --git a/packages/kbn-docs-utils/src/build_api_declarations/extract_import_refs.ts b/packages/kbn-docs-utils/src/build_api_declarations/extract_import_refs.ts index e72367f5fd783d..46acb50bd17add 100644 --- a/packages/kbn-docs-utils/src/build_api_declarations/extract_import_refs.ts +++ b/packages/kbn-docs-utils/src/build_api_declarations/extract_import_refs.ts @@ -59,9 +59,9 @@ export function extractImportReferences( id: name, }); texts.push({ - pluginId: plugin.manifest.id, + pluginId: plugin.id, scope: getScopeFromPath(path, plugin, log), - docId: getPluginApiDocId(plugin.manifest.id, { + docId: getPluginApiDocId(plugin.id, { serviceFolders: plugin.manifest.serviceFolders, apiPath: path, directory: plugin.directory, diff --git a/packages/kbn-docs-utils/src/build_api_declarations/get_references.ts b/packages/kbn-docs-utils/src/build_api_declarations/get_references.ts index d92adbcac37d25..904bbb3a654548 100644 --- a/packages/kbn-docs-utils/src/build_api_declarations/get_references.ts +++ b/packages/kbn-docs-utils/src/build_api_declarations/get_references.ts @@ -35,9 +35,9 @@ export function getReferences({ node, plugins, currentPluginId, log }: Opts): Ap // Don't include references from inside the plugin itself, we only care about // external references (if it's only used internally, it shouldn't be exported). - if (refPlugin && refPlugin.manifest.id !== currentPluginId) { + if (refPlugin && refPlugin.id !== currentPluginId) { refs.push({ - plugin: refPlugin.manifest.id, + plugin: refPlugin.id, path: getSourceForNode(ref), }); } diff --git a/packages/kbn-docs-utils/src/build_api_docs_cli.ts b/packages/kbn-docs-utils/src/build_api_docs_cli.ts index 620097c12b3b3d..c5db0718927cac 100644 --- a/packages/kbn-docs-utils/src/build_api_docs_cli.ts +++ b/packages/kbn-docs-utils/src/build_api_docs_cli.ts @@ -94,7 +94,7 @@ export function runBuildApiDocsCli() { const allPluginStats: { [key: string]: PluginMetaInfo & ApiStats & EslintDisableCounts } = {}; for (const plugin of plugins) { - const id = plugin.manifest.id; + const id = plugin.id; const pluginApi = pluginApiMap[id]; const paths = pathsByPlugin.get(plugin) ?? []; @@ -118,11 +118,11 @@ export function runBuildApiDocsCli() { // Note that the filtering is done here, and not above because the entire public plugin API has to // be parsed in order to correctly determine reference links, and ensure that `removeBrokenLinks` // doesn't remove more links than necessary. - if (pluginFilter && !pluginFilter.includes(plugin.manifest.id)) { + if (pluginFilter && !pluginFilter.includes(plugin.id)) { continue; } - const id = plugin.manifest.id; + const id = plugin.id; const pluginApi = pluginApiMap[id]; const pluginStats = allPluginStats[id]; const pluginTeam = plugin.manifest.owner.name; @@ -212,13 +212,13 @@ export function runBuildApiDocsCli() { d.label )}`; - if (collectReferences && pluginFilter?.includes(plugin.manifest.id)) { + if (collectReferences && pluginFilter?.includes(plugin.id)) { if (referencedDeprecations[id] && pluginStats.deprecatedAPIsReferencedCount > 0) { log.info(`${referencedDeprecations[id].length} deprecated APIs used`); // eslint-disable-next-line no-console console.table(referencedDeprecations[id]); } else { - log.info(`No referenced deprecations for plugin ${plugin.manifest.id}`); + log.info(`No referenced deprecations for plugin ${plugin.id}`); } if (pluginStats.noReferences.length > 0) { // eslint-disable-next-line no-console @@ -229,7 +229,7 @@ export function runBuildApiDocsCli() { })) ); } else { - log.info(`No unused APIs for plugin ${plugin.manifest.id}`); + log.info(`No unused APIs for plugin ${plugin.id}`); } } diff --git a/packages/kbn-docs-utils/src/find_plugins.ts b/packages/kbn-docs-utils/src/find_plugins.ts index a6f0a75cfe7a5d..30a63b55172e12 100644 --- a/packages/kbn-docs-utils/src/find_plugins.ts +++ b/packages/kbn-docs-utils/src/find_plugins.ts @@ -7,78 +7,73 @@ */ import Path from 'path'; -import globby from 'globby'; -import loadJsonFile from 'load-json-file'; - -import { getPluginSearchPaths, simpleKibanaPlatformPluginDiscovery } from '@kbn/plugin-discovery'; +import { getPackages, getPluginPackagesFilter, type Package } from '@kbn/repo-packages'; import { REPO_ROOT } from '@kbn/repo-info'; import { ApiScope, PluginOrPackage } from './types'; +function toApiScope(pkg: Package): ApiScope { + switch (pkg.manifest.type) { + case 'shared-browser': + case 'shared-scss': + return ApiScope.CLIENT; + case 'shared-server': + return ApiScope.SERVER; + case 'test-helper': + case 'functional-tests': + case 'shared-common': + return ApiScope.COMMON; + case 'plugin': + return pkg.manifest.plugin.server && !pkg.manifest.plugin.browser + ? ApiScope.SERVER + : !pkg.manifest.plugin.server && pkg.manifest.plugin.browser + ? ApiScope.CLIENT + : ApiScope.COMMON; + } +} + +function toPluginOrPackage(pkg: Package): PluginOrPackage { + return { + id: pkg.isPlugin() ? pkg.manifest.plugin.id : pkg.manifest.id, + directory: Path.resolve(REPO_ROOT, pkg.normalizedRepoRelativeDir), + manifestPath: Path.resolve(REPO_ROOT, pkg.normalizedRepoRelativeDir, 'kibana.jsonc'), + isPlugin: pkg.isPlugin(), + manifest: { + id: pkg.isPlugin() ? pkg.manifest.plugin.id : pkg.manifest.id, + pluginId: pkg.isPlugin() ? pkg.manifest.plugin.id : undefined, + owner: { + name: pkg.manifest.owner?.[0] ?? '[Owner missing]', + githubTeam: pkg.manifest.owner?.[0]?.split('@elastic/')[1], + }, + serviceFolders: pkg.manifest.serviceFolders || [], + description: pkg.manifest.description || undefined, + }, + scope: toApiScope(pkg), + }; +} + export function findPlugins(): PluginOrPackage[] { - const pluginSearchPaths = getPluginSearchPaths({ - rootDir: REPO_ROOT, - oss: false, - examples: false, - }); + const packages = getPackages(REPO_ROOT); + const plugins = packages.filter( + getPluginPackagesFilter({ + examples: false, + testPlugins: false, + }) + ); + const core = packages.find((p) => p.manifest.id === '@kbn/core'); + + if (!core) { + throw new Error('unable to find @kbn/core'); + } - return ( - simpleKibanaPlatformPluginDiscovery(pluginSearchPaths, [ - // discover "core" as a plugin - Path.resolve(REPO_ROOT, 'src/core'), - ]).map((p) => ({ ...p, isPlugin: true, importPath: p.directory })) as PluginOrPackage[] - ).concat(...findPackages()); + return [...[core, ...plugins].map(toPluginOrPackage), ...findPackages()]; } /** * Helper to find packages. */ export function findPackages(): PluginOrPackage[] { - const packagePaths = globby - .sync(Path.resolve(REPO_ROOT, '{x-pack/,}packages/**/package.json'), { absolute: true }) - .map((path) => - // absolute paths returned from globby are using normalize or - // something so the path separators are `/` even on windows, - // Path.resolve solves this - Path.resolve(path) - ); - - if (packagePaths.length === 0) { - throw new Error('No packages found!'); - } - - return packagePaths.reduce((acc, path) => { - const manifest: { name: string; author?: string; main?: string; browser?: string } = - loadJsonFile.sync(path); - if (manifest.name === undefined) return acc; - - let scope = ApiScope.COMMON; - if (manifest.main && !manifest.browser) { - scope = ApiScope.SERVER; - } else if (manifest.browser && !manifest.main) { - scope = ApiScope.CLIENT; - } - - let ownerName = '[Owner missing]'; - // Some of these author fields have "" in the name which mdx chokes on. Removing the < and > seems to work. - if (Array.isArray(manifest.author)) { - ownerName = manifest.author.map((d) => d.replace(/[<>]/gi, '')).join(', '); - } else if (typeof manifest.author === 'string') { - ownerName = manifest.author.replace(/[<>]/gi, ''); - } - - acc.push({ - directory: Path.dirname(path), - manifestPath: path, - manifest: { - ...manifest, - id: manifest.name, - serviceFolders: [], - owner: { name: ownerName }, - }, - isPlugin: false, - scope, - }); - return acc; - }, [] as PluginOrPackage[]); + return getPackages(REPO_ROOT) + .filter((p) => !p.isPlugin()) + .map(toPluginOrPackage); } diff --git a/packages/kbn-docs-utils/src/get_plugin_api.ts b/packages/kbn-docs-utils/src/get_plugin_api.ts index b8850059aa366a..b4a660e8db617e 100644 --- a/packages/kbn-docs-utils/src/get_plugin_api.ts +++ b/packages/kbn-docs-utils/src/get_plugin_api.ts @@ -29,7 +29,7 @@ export function getPluginApi( const server = getDeclarations(project, plugin, ApiScope.SERVER, plugins, log, captureReferences); const common = getDeclarations(project, plugin, ApiScope.COMMON, plugins, log, captureReferences); return { - id: plugin.manifest.id, + id: plugin.id, client, server, common, @@ -58,7 +58,7 @@ function getDeclarations( const apiDec = buildApiDeclarationTopNode(node, { plugins, log, - currentPluginId: plugin.manifest.id, + currentPluginId: plugin.id, scope, captureReferences, }); diff --git a/packages/kbn-docs-utils/src/get_plugin_api_map.ts b/packages/kbn-docs-utils/src/get_plugin_api_map.ts index 974941f09c0624..ee8d6301495511 100644 --- a/packages/kbn-docs-utils/src/get_plugin_api_map.ts +++ b/packages/kbn-docs-utils/src/get_plugin_api_map.ts @@ -37,14 +37,8 @@ export function getPluginApiMap( const pluginApiMap: { [key: string]: PluginApi } = {}; plugins.forEach((plugin) => { const captureReferences = - collectReferences && (!pluginFilter || pluginFilter.indexOf(plugin.manifest.id) >= 0); - pluginApiMap[plugin.manifest.id] = getPluginApi( - project, - plugin, - plugins, - log, - captureReferences - ); + collectReferences && (!pluginFilter || pluginFilter.indexOf(plugin.id) >= 0); + pluginApiMap[plugin.id] = getPluginApi(project, plugin, plugins, log, captureReferences); }); // Mapping of plugin id to the missing source API id to all the plugin API items that referenced this item. @@ -54,7 +48,7 @@ export function getPluginApiMap( const adoptionTrackedAPIs: AdoptionTrackedAPIsByPlugin = {}; plugins.forEach((plugin) => { - const id = plugin.manifest.id; + const id = plugin.id; const pluginApi = pluginApiMap[id]; removeBrokenLinks(pluginApi, missingApiItems, pluginApiMap, log); collectDeprecations(pluginApi, referencedDeprecations, unreferencedDeprecations); diff --git a/packages/kbn-docs-utils/src/integration_tests/kibana_platform_plugin_mock.ts b/packages/kbn-docs-utils/src/integration_tests/kibana_platform_plugin_mock.ts index c373b9be0c6ffd..1df23fd806f8bb 100644 --- a/packages/kbn-docs-utils/src/integration_tests/kibana_platform_plugin_mock.ts +++ b/packages/kbn-docs-utils/src/integration_tests/kibana_platform_plugin_mock.ts @@ -12,6 +12,7 @@ import { PluginOrPackage } from '../types'; export function getKibanaPlatformPlugin(id: string, dir?: string): PluginOrPackage { const directory = dir ?? Path.resolve(__dirname, '__fixtures__/src/plugin_a'); return { + id, manifest: { id, owner: { @@ -28,6 +29,7 @@ export function getKibanaPlatformPlugin(id: string, dir?: string): PluginOrPacka export function getKibanaPlatformPackage(id: string, importPath?: string): PluginOrPackage { const directory = Path.resolve(__dirname, '__fixtures__/src/plugin_a'); return { + id, manifest: { id, owner: { diff --git a/packages/kbn-docs-utils/src/mdx/write_deprecations_due_by_team.ts b/packages/kbn-docs-utils/src/mdx/write_deprecations_due_by_team.ts index d225f5d54693d1..ca84ec88dc7907 100644 --- a/packages/kbn-docs-utils/src/mdx/write_deprecations_due_by_team.ts +++ b/packages/kbn-docs-utils/src/mdx/write_deprecations_due_by_team.ts @@ -32,7 +32,7 @@ export async function writeDeprecationDueByTeam( ); if (!dueDeprecations || dueDeprecations.length === 0) return teamMap; - const pluginMetaInfo = plugins.find((p) => p.manifest.id === pluginId); + const pluginMetaInfo = plugins.find((p) => p.id === pluginId); if (!pluginMetaInfo || !pluginMetaInfo.manifest.owner.name) return teamMap; if (!teamMap[pluginMetaInfo.manifest.owner.name]) { diff --git a/packages/kbn-docs-utils/src/types.ts b/packages/kbn-docs-utils/src/types.ts index 09ff30b9b9da25..40faadb8c41f12 100644 --- a/packages/kbn-docs-utils/src/types.ts +++ b/packages/kbn-docs-utils/src/types.ts @@ -7,8 +7,10 @@ */ export interface PluginOrPackage { + id: string; manifest: { id: string; + pluginId?: string; description?: string; owner: { name: string; githubTeam?: string }; serviceFolders: readonly string[]; diff --git a/packages/kbn-docs-utils/src/utils.test.ts b/packages/kbn-docs-utils/src/utils.test.ts index 5ae2c1db624950..7de66dfa949f6d 100644 --- a/packages/kbn-docs-utils/src/utils.test.ts +++ b/packages/kbn-docs-utils/src/utils.test.ts @@ -77,13 +77,13 @@ it('test removeBrokenLinks', () => { const pluginApiMap: { [key: string]: PluginApi } = {}; plugins.map((plugin) => { - pluginApiMap[plugin.manifest.id] = getPluginApi(project, plugin, plugins, log, false); + pluginApiMap[plugin.id] = getPluginApi(project, plugin, plugins, log, false); }); const missingApiItems: { [key: string]: { [key: string]: string[] } } = {}; plugins.forEach((plugin) => { - const id = plugin.manifest.id; + const id = plugin.id; const pluginApi = pluginApiMap[id]; removeBrokenLinks(pluginApi, missingApiItems, pluginApiMap, log); }); diff --git a/packages/kbn-docs-utils/src/utils.ts b/packages/kbn-docs-utils/src/utils.ts index c479228533f495..a7de65b68ad0f3 100644 --- a/packages/kbn-docs-utils/src/utils.ts +++ b/packages/kbn-docs-utils/src/utils.ts @@ -37,7 +37,7 @@ export function getPluginForPath( ): PluginOrPackage | undefined { if (filePath.indexOf('@') >= 0) { return plugins.find( - (plugin) => !plugin.isPlugin && filePath.indexOf(plugin.manifest.id + path.sep) >= 0 + (plugin) => !plugin.isPlugin && filePath.indexOf(plugin.id + path.sep) >= 0 ); } else { return plugins.find((plugin) => filePath.startsWith(plugin.directory + path.sep)); diff --git a/packages/kbn-docs-utils/tsconfig.json b/packages/kbn-docs-utils/tsconfig.json index 6f64737de0d4dc..fa23110abcb1fd 100644 --- a/packages/kbn-docs-utils/tsconfig.json +++ b/packages/kbn-docs-utils/tsconfig.json @@ -15,7 +15,6 @@ "target/**/*", ], "kbn_references": [ - "@kbn/plugin-discovery", "@kbn/tooling-log", "@kbn/dev-cli-runner", "@kbn/dev-cli-errors", @@ -23,5 +22,6 @@ "@kbn/repo-info", "@kbn/std", "@kbn/get-repo-files", + "@kbn/repo-packages", ] } diff --git a/packages/kbn-eslint-config/package.json b/packages/kbn-eslint-config/package.json index 76082ec00ee44f..8ad447c6c2f2c3 100644 --- a/packages/kbn-eslint-config/package.json +++ b/packages/kbn-eslint-config/package.json @@ -9,7 +9,7 @@ }, "keywords": [], "author": "Spencer Alger ", - "license": "Apache-2.0", + "license": "SSPL-1.0 OR Elastic License 2.0", "bugs": { "url": "https://github.com/elastic/kibana/tree/main/packages/kbn-eslint-config" }, diff --git a/packages/kbn-eslint-plugin-imports/src/rules/no_boundary_crossing.ts b/packages/kbn-eslint-plugin-imports/src/rules/no_boundary_crossing.ts index 832b4d2e3c67e9..db4be34ba01093 100644 --- a/packages/kbn-eslint-plugin-imports/src/rules/no_boundary_crossing.ts +++ b/packages/kbn-eslint-plugin-imports/src/rules/no_boundary_crossing.ts @@ -19,17 +19,17 @@ import { getSourcePath } from '../helpers/source'; import { getRepoSourceClassifier } from '../helpers/repo_source_classifier'; import { getImportResolver } from '../get_import_resolver'; -const ANY_FILE_IN_BAZEL = Symbol(); +const ANY = Symbol(); -const IMPORTABLE_FROM: Record = { +const IMPORTABLE_FROM: Record = { 'non-package': ['non-package', 'server package', 'browser package', 'common package', 'static'], 'server package': ['common package', 'server package', 'static'], 'browser package': ['common package', 'browser package', 'static'], 'common package': ['common package', 'static'], static: [], - 'tests or mocks': ANY_FILE_IN_BAZEL, - tooling: ANY_FILE_IN_BAZEL, + 'tests or mocks': ANY, + tooling: ANY, }; const toList = (strings: string[]) => { @@ -89,7 +89,6 @@ export const NoBoundaryCrossingRule: Rule.RuleModule = { }, messages: { TYPE_MISMATCH: `"{{importedType}}" code can not be imported from "{{ownType}}" code.{{suggestion}}`, - FILE_OUTSIDE_OF_PACKAGE: `"{{ownType}}" code can import any code already within packages, but not files outside of packages.`, }, }, create(context) { @@ -119,24 +118,7 @@ export const NoBoundaryCrossingRule: Rule.RuleModule = { const imported = classifier.classify(result.absolute); - if (importable === ANY_FILE_IN_BAZEL) { - if (type === 'jest' && imported.repoRel === 'package.json') { - // we allow jest.mock() calls to mock out the `package.json` file... it's a very - // specific exception for a very specific implementation - return; - } - - if (self.pkgInfo?.isBazelPackage ? imported.pkgInfo?.isBazelPackage : true) { - return; - } - - context.report({ - node: node as ESTree.Node, - messageId: 'FILE_OUTSIDE_OF_PACKAGE', - data: { - ownType: self.type, - }, - }); + if (importable === ANY) { return; } diff --git a/packages/kbn-eslint-plugin-imports/src/rules/uniform_imports.ts b/packages/kbn-eslint-plugin-imports/src/rules/uniform_imports.ts index d853c791640e5c..6855a1359f43eb 100644 --- a/packages/kbn-eslint-plugin-imports/src/rules/uniform_imports.ts +++ b/packages/kbn-eslint-plugin-imports/src/rules/uniform_imports.ts @@ -9,7 +9,6 @@ import Path from 'path'; import Eslint from 'eslint'; -import { REPO_ROOT } from '@kbn/repo-info'; import { getRelativeImportReq, getPackageRelativeImportReq } from '@kbn/import-resolver'; import { report } from '../helpers/report'; @@ -17,13 +16,6 @@ import { visitAllImportStatements } from '../helpers/visit_all_import_statements import { getSourcePath } from '../helpers/source'; import { getImportResolver } from '../get_import_resolver'; -// TODO: get rid of all the special cases in here by moving more things to packages - -const SETUP_NODE_ENV_DIR = Path.resolve(REPO_ROOT, 'src/setup_node_env'); -const PKGJSON_PATH = Path.resolve(REPO_ROOT, 'package.json'); -const XPACK_PKGJSON_PATH = Path.resolve(REPO_ROOT, 'x-pack/package.json'); -const KBN_PM_SCRIPT = Path.resolve(REPO_ROOT, 'packages/kbn-pm/dist/index.js'); - export const UniformImportsRule: Eslint.Rule.RuleModule = { meta: { fixable: 'code', @@ -48,33 +40,8 @@ export const UniformImportsRule: Eslint.Rule.RuleModule = { return; } - const { absolute } = result; - // don't mess with imports to the kbn/pm script for now - if (absolute === KBN_PM_SCRIPT) { - return; - } - const { pkgId } = result; - if (ownPackageId && !pkgId) { - // special cases, files that aren't in packages but packages are allowed to import them - if ( - absolute === PKGJSON_PATH || - absolute === XPACK_PKGJSON_PATH || - absolute.startsWith(SETUP_NODE_ENV_DIR) - ) { - return; - } - - if (resolver.isBazelPackage(ownPackageId)) { - report(context, { - node, - message: `Package [${ownPackageId}] can only import other packages`, - }); - return; - } - } - if (pkgId === ownPackageId || !pkgId) { const correct = getRelativeImportReq({ ...result, diff --git a/packages/kbn-find-used-node-modules/src/find_used_node_modules.test.ts b/packages/kbn-find-used-node-modules/src/find_used_node_modules.test.ts index c336927c9e6d3c..86665557605d87 100644 --- a/packages/kbn-find-used-node-modules/src/find_used_node_modules.test.ts +++ b/packages/kbn-find-used-node-modules/src/find_used_node_modules.test.ts @@ -6,47 +6,44 @@ * Side Public License, v 1. */ -import * as Rx from 'rxjs'; import { findUsedNodeModules } from './find_used_node_modules'; import { ImportResolver } from '@kbn/import-resolver'; +import { ImportLocator } from '@kbn/import-locator'; jest.mock('./fs'); -const FILES: Record = { - '/foo.js': ` - require('./bar.js') - `, - '/bar.js': ` - require('./box') - `, - '/box.js': ` - require('foo') - `, -}; - class MockResolver extends ImportResolver { constructor() { - super('/', new Map(), new Map()); + super('/', new Map()); } - isBazelPackage = jest.fn(); resolve = jest.fn(); } +class MockLocator extends ImportLocator { + read = jest.fn(); +} + +const REQS: Map = new Map( + Object.entries({ + '/foo.js': ['./bar.js'], + '/bar.js': ['./box'], + '/box.js': ['foo'], + }) +); + const RESOLVER = new MockResolver(); +const LOCATOR = new MockLocator(); beforeEach(() => { jest.resetAllMocks(); - jest.requireMock('./fs').readFile$.mockImplementation((path: string) => { - if (Object.hasOwn(FILES, path)) { - return Rx.of(FILES[path]); - } - return Rx.throwError(() => { - const error: any = new Error(`ENOENT, missing file [${path}]`); - error.code = 'ENOENT'; - return error; - }); + LOCATOR.read.mockImplementation(async (path: string) => { + const reqs = REQS.get(path); + if (reqs === undefined) { + throw new Error('unexpected path passed to ImportLocator#read()'); + } + return new Set(reqs); }); }); @@ -58,6 +55,7 @@ describe('findUsedNodeModules()', () => { const results = await findUsedNodeModules({ entryPaths: ['/foo.js'], + locator: LOCATOR, resolver: RESOLVER, findUsedPeers: false, }); @@ -81,6 +79,7 @@ describe('findUsedNodeModules()', () => { const results = await findUsedNodeModules({ entryPaths: ['/foo.js'], + locator: LOCATOR, resolver: RESOLVER, findUsedPeers: false, }); @@ -110,6 +109,7 @@ describe('findUsedNodeModules()', () => { const results = await findUsedNodeModules({ entryPaths: ['/foo.js'], + locator: LOCATOR, resolver: RESOLVER, findUsedPeers: false, }); @@ -139,11 +139,13 @@ describe('findUsedNodeModules()', () => { const results = await findUsedNodeModules({ entryPaths: ['/foo.js'], + locator: LOCATOR, resolver: RESOLVER, findUsedPeers: false, }); - expect(RESOLVER.resolve).toHaveBeenCalledTimes(2); expect(results).toEqual(['@foo/box']); + expect(RESOLVER.resolve).toHaveBeenCalledTimes(2); + expect(LOCATOR.read).toHaveBeenCalledTimes(2); }); it('does traverse node_modules which are also bazel packages', async () => { @@ -158,7 +160,7 @@ describe('findUsedNodeModules()', () => { if (req === './box') { return { type: 'file', - nodeModule: '@foo/box', + pkgId: '@foo/box', absolute: '/box.js', }; } @@ -174,16 +176,14 @@ describe('findUsedNodeModules()', () => { throw new Error('unexpected request'); }); - RESOLVER.isBazelPackage.mockImplementation((pkgId) => { - return pkgId === '@foo/box'; - }); - const results = await findUsedNodeModules({ entryPaths: ['/foo.js'], + locator: LOCATOR, resolver: RESOLVER, findUsedPeers: false, }); expect(RESOLVER.resolve).toHaveBeenCalledTimes(3); + expect(LOCATOR.read).toHaveBeenCalledTimes(3); expect(results).toEqual(['@foo/box', '@foo/core']); }); }); diff --git a/packages/kbn-find-used-node-modules/src/find_used_node_modules.ts b/packages/kbn-find-used-node-modules/src/find_used_node_modules.ts index 0ecb7f10bb090a..403532d0fc529b 100644 --- a/packages/kbn-find-used-node-modules/src/find_used_node_modules.ts +++ b/packages/kbn-find-used-node-modules/src/find_used_node_modules.ts @@ -7,9 +7,12 @@ */ import Path from 'path'; +import Fs from 'fs'; import * as Rx from 'rxjs'; import type { ImportResolver } from '@kbn/import-resolver'; +import type { ImportLocator } from '@kbn/import-locator'; +import { REPO_ROOT } from '@kbn/repo-info'; import { readFile$ } from './fs'; @@ -17,7 +20,32 @@ function isObj(v: any): v is Record { return typeof v === 'object' && v !== null; } +function findPkgJsonUpDeep(path: string): string | null { + const dir = Path.dirname(path); + if (dir === '/' || dir === REPO_ROOT || dir === path) { + return null; + } + + const candidate = Path.resolve(dir, 'package.json'); + if (Fs.existsSync(candidate)) { + return candidate; + } + + return findPkgJsonUpDeep(dir); +} + +function findPkgJson(dep: string, paths: Iterable) { + for (const path of paths) { + const value = findPkgJsonUpDeep(path); + if (value !== null) { + return value; + } + } + throw new Error(`Unable to find package.json file for ${dep}`); +} + interface Options { + locator: ImportLocator; resolver: ImportResolver; entryPaths: string[]; findUsedPeers: boolean; @@ -44,8 +72,6 @@ interface Options { * was much slower and lead to extra entries in package.json. */ -import { getImportRequests } from './get_import_requests'; - export async function findUsedNodeModules(options: Options) { const results = new Set(); const entryPathsIntoNodeModules = new Map>(); @@ -61,13 +87,12 @@ export async function findUsedNodeModules(options: Options) { Rx.tap(() => { inputs += 1; }), - Rx.mergeMap((path) => readFile$(path, 'utf8').pipe(Rx.map((code) => ({ code, path })))), - Rx.mergeMap(async ({ path, code }) => { - const reqs = getImportRequests(code); + Rx.mergeMap(async (path) => { + const reqs = await options.locator.read(path); const dirname = Path.dirname(path); for (const req of reqs) { - // resolve the request to it's actual file on dist + // resolve the request to it's actual file on disk const result = options.resolver.resolve(req, dirname); // ignore non-file resolution results, these represent files which aren't on @@ -102,13 +127,13 @@ export async function findUsedNodeModules(options: Options) { if ( !result.nodeModule || result.nodeModule === options.thisNodeModule || - options.resolver.isBazelPackage(result.nodeModule) + options.resolver.isRepoPkg(result.nodeModule) ) { path$.next(result.absolute); continue; } } - }, 40), + }, 200), Rx.tap(() => { outputs += 1; if (inputs === outputs) { @@ -133,8 +158,7 @@ export async function findUsedNodeModules(options: Options) { return Rx.EMPTY; } - const pkgPath = require.resolve(`${dep}/package.json`); - return readFile$(pkgPath, 'utf8').pipe( + return readFile$(findPkgJson(dep, entryPaths), 'utf8').pipe( Rx.mergeMap((pkgJson) => { const pkg = JSON.parse(pkgJson); @@ -153,6 +177,7 @@ export async function findUsedNodeModules(options: Options) { }, 50), Rx.concatMap(async ({ entryPaths, dep, peerDeps }) => { const usedInside = await findUsedNodeModules({ + locator: options.locator, resolver: options.resolver, entryPaths: Array.from(entryPaths), findUsedPeers: false, diff --git a/packages/kbn-find-used-node-modules/tsconfig.json b/packages/kbn-find-used-node-modules/tsconfig.json index b43f686f338548..22a4d189a4145d 100644 --- a/packages/kbn-find-used-node-modules/tsconfig.json +++ b/packages/kbn-find-used-node-modules/tsconfig.json @@ -13,6 +13,8 @@ "kbn_references": [ "@kbn/import-resolver", "@kbn/babel-preset", + "@kbn/import-locator", + "@kbn/repo-info", ], "exclude": [ "target/**/*", diff --git a/packages/kbn-generate/src/commands/codeowners_command.ts b/packages/kbn-generate/src/commands/codeowners_command.ts index 47352996886606..733e123815dce2 100644 --- a/packages/kbn-generate/src/commands/codeowners_command.ts +++ b/packages/kbn-generate/src/commands/codeowners_command.ts @@ -26,24 +26,57 @@ const GENERATED_START = ` `; +const GENERATED_TRAILER = ` + +# Design (at the bottom for specificity of SASS files) +**/*.scss @elastic/kibana-design +`; + +function normalizeDir(dirish: string): string { + const trim = dirish.trim(); + if (trim.startsWith('/')) { + return normalizeDir(trim.slice(1)); + } + + if (trim.endsWith('/')) { + return normalizeDir(trim.slice(0, -1)); + } + + return trim; +} + export const CodeownersCommand: GenerateCommand = { name: 'codeowners', description: 'Update the codeowners file based on the package manifest files', usage: 'node scripts/generate codeowners', async run({ log }) { + const pkgs = getPackages(REPO_ROOT); const coPath = Path.resolve(REPO_ROOT, REL); const codeowners = await Fsp.readFile(coPath, 'utf8'); - const pkgs = getPackages(REPO_ROOT); - - let genStart = codeowners.indexOf(GENERATED_START); + let genStart: number | undefined = codeowners.indexOf(GENERATED_START); if (genStart === -1) { - genStart = codeowners.length; + genStart = undefined; log.warning(`${REL} doesn't include the expected start-marker for injecting generated text`); } - const newCodeowners = `${codeowners.slice(0, genStart)}${GENERATED_START}${pkgs + const pkgDirs = new Set(pkgs.map((pkg) => pkg.normalizedRepoRelativeDir)); + const lines = []; + + for (const line of codeowners.slice(0, genStart).split('\n')) { + if (line.startsWith('#') || !line.trim()) { + lines.push(line); + continue; + } + + const dir = normalizeDir(line.split('@')[0]); + if (!pkgDirs.has(dir)) { + lines.push(line); + } + } + + const newCodeowners = `${lines.join('\n')}${GENERATED_START}${pkgs .map((pkg) => `${pkg.normalizedRepoRelativeDir} ${pkg.manifest.owner.join(' ')}`) - .join('\n')}\n`; + .join('\n')}${GENERATED_TRAILER}`; if (codeowners === newCodeowners) { log.success(`${REL} is already up-to-date`); diff --git a/packages/kbn-generate/src/commands/package_command.ts b/packages/kbn-generate/src/commands/package_command.ts index 115834dd143402..efa92cc7d9301a 100644 --- a/packages/kbn-generate/src/commands/package_command.ts +++ b/packages/kbn-generate/src/commands/package_command.ts @@ -171,7 +171,7 @@ export const PackageCommand: GenerateCommand = { addDeps[pkgId] = `link:${normalizedRepoRelativeDir}`; delete removeDeps[pkgId]; - await Fsp.writeFile(packageJsonPath, sortPackageJson(JSON.stringify(packageJson))); + await Fsp.writeFile(packageJsonPath, sortPackageJson(packageJson)); log.info('Updated package.json file'); log.success(`Generated ${pkgId}! Please bootstrap to make sure it works.`); diff --git a/packages/kbn-get-repo-files/get_repo_files.ts b/packages/kbn-get-repo-files/get_repo_files.ts new file mode 100644 index 00000000000000..5d85551fa3e34b --- /dev/null +++ b/packages/kbn-get-repo-files/get_repo_files.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { RepoPath } from '@kbn/repo-path'; +import { REPO_ROOT } from '@kbn/repo-info'; +import { getRepoRels } from '@kbn/repo-packages'; + +/** + * List the files in the repo, only including files which are manged by version + * control or "untracked" (new, not committed, and not ignored). + * @param include limit the list to specfic absolute paths + * @param exclude exclude specific absolute paths + */ +export async function getRepoFiles(include?: string[], exclude?: string[]) { + return Array.from( + await getRepoRels(REPO_ROOT, include, exclude), + (rel) => new RepoPath(REPO_ROOT, rel) + ); +} diff --git a/packages/kbn-get-repo-files/index.ts b/packages/kbn-get-repo-files/index.ts deleted file mode 100644 index 0d33a512abda59..00000000000000 --- a/packages/kbn-get-repo-files/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export { getRepoFiles, getRepoFilesSync } from './src/get_repo_files'; diff --git a/packages/kbn-get-repo-files/package.json b/packages/kbn-get-repo-files/package.json index d16a1b7dbe6685..ce7f14b2a601ae 100644 --- a/packages/kbn-get-repo-files/package.json +++ b/packages/kbn-get-repo-files/package.json @@ -2,5 +2,6 @@ "name": "@kbn/get-repo-files", "private": true, "version": "1.0.0", - "license": "SSPL-1.0 OR Elastic License 2.0" + "license": "SSPL-1.0 OR Elastic License 2.0", + "main": "./get_repo_files" } diff --git a/packages/kbn-get-repo-files/src/get_repo_files.ts b/packages/kbn-get-repo-files/src/get_repo_files.ts deleted file mode 100644 index b68e1b1e40bdfb..00000000000000 --- a/packages/kbn-get-repo-files/src/get_repo_files.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import Path from 'path'; -import Fs from 'fs'; - -import execa from 'execa'; -import { REPO_ROOT } from '@kbn/repo-info'; -import { RepoPath } from '@kbn/repo-path'; - -function parseLsFilesOutput(output: string) { - const paths = new Map(); - const files = new Set(); - - for (const line of output.split('\n').map((l) => l.trim())) { - if (!line) { - continue; - } - - const repoRel = line.slice(2); // trim the single char status and separating space from the line - const existingPath = paths.get(repoRel); - const path = existingPath ?? new RepoPath(REPO_ROOT, repoRel); - if (!existingPath) { - paths.set(repoRel, path); - } - - if (line.startsWith('C ')) { - // this line indicates that the previous path is changed in the working - // tree, so we need to determine if it was deleted and remove it if so - if (!Fs.existsSync(path.abs)) { - files.delete(path); - } - } else { - files.add(path); - } - } - - return files; -} - -function getGitFlags(include?: string[], exclude?: string[]) { - return [ - 'ls-files', - '-comt', - '--exclude-standard', - include?.map((p) => Path.relative(REPO_ROOT, p)) ?? [], - exclude?.map((p) => `--exclude=${Path.relative(REPO_ROOT, p)}`) ?? [], - ].flat(); -} - -/** - * List the files in the repo, only including files which are manged by version - * control or "untracked" (new, not committed, and not ignored). - * @param include limit the list to specfic absolute paths - * @param exclude exclude specific absolute paths - */ -export async function getRepoFiles(include?: string[], exclude?: string[]) { - const proc = await execa('git', getGitFlags(include, exclude), { - cwd: REPO_ROOT, - stdio: ['ignore', 'pipe', 'pipe'], - buffer: true, - }); - - return parseLsFilesOutput(proc.stdout); -} - -/** - * Synchronously list the files in the repo, only including files which are manged by version - * control or "untracked" (new, not committed, and not ignored). - * @param include limit the list to specfic absolute paths - * @param exclude exclude specific absolute paths - */ -export function getRepoFilesSync(include?: string[], exclude?: string[]) { - const proc = execa.sync('git', getGitFlags(include, exclude), { - cwd: REPO_ROOT, - stdio: ['ignore', 'pipe', 'pipe'], - buffer: true, - }); - - return parseLsFilesOutput(proc.stdout); -} diff --git a/packages/kbn-get-repo-files/tsconfig.json b/packages/kbn-get-repo-files/tsconfig.json index 4c74f7e7042c50..f561cd779b3a97 100644 --- a/packages/kbn-get-repo-files/tsconfig.json +++ b/packages/kbn-get-repo-files/tsconfig.json @@ -2,17 +2,20 @@ "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "target/types", + "checkJs": true, "types": [ "jest", "node" ] }, "include": [ + "**/*.js", "**/*.ts" ], "kbn_references": [ "@kbn/repo-info", "@kbn/repo-path", + "@kbn/repo-packages", ], "exclude": [ "target/**/*", diff --git a/packages/kbn-import-locator/import_locator.ts b/packages/kbn-import-locator/import_locator.ts index 053fbc48a354a3..2cadc2a64c06a6 100644 --- a/packages/kbn-import-locator/import_locator.ts +++ b/packages/kbn-import-locator/import_locator.ts @@ -30,9 +30,9 @@ export class ImportLocator { } const imports = new Set(); - const queue: Ts.Node[] = [ - Ts.createSourceFile(path, strippedContent, Ts.ScriptTarget.Latest, true), - ]; + const sourceFile = Ts.createSourceFile(path, strippedContent, Ts.ScriptTarget.Latest, true); + + const queue: Ts.Node[] = [sourceFile]; const addNodeToQueue = (n: Ts.Node) => { queue.push(n); }; diff --git a/packages/kbn-import-locator/strip_source_code.ts b/packages/kbn-import-locator/strip_source_code.ts index f12810970c49b3..68944d216b43fc 100644 --- a/packages/kbn-import-locator/strip_source_code.ts +++ b/packages/kbn-import-locator/strip_source_code.ts @@ -35,31 +35,30 @@ import { SyntaxKind } from 'typescript'; import type { Scanner } from 'typescript'; +const TYPE_IMPORT_COMMENT_RE = /\{(.*import.*\(.*["'].*)\}/g; + export function stripSourceCode(scanner: Scanner, contents: string): string { scanner.setText(contents); let token = scanner.scan(); - const statements = []; + const statements: string[] = []; let start = null; while (token !== SyntaxKind.EndOfFileToken) { const potentialStart = scanner.getStartPos(); switch (token) { case SyntaxKind.MultiLineCommentTrivia: case SyntaxKind.SingleLineCommentTrivia: { - const isMultiLineCommentTrivia = token === SyntaxKind.MultiLineCommentTrivia; - const start = potentialStart + 2; + const isMultiline = token === SyntaxKind.MultiLineCommentTrivia; + const start = potentialStart; token = scanner.scan(); - const end = scanner.getStartPos() - (isMultiLineCommentTrivia ? 2 : 0); - const comment = contents.substring(start, end).trim(); - if (comment === 'nx-ignore-next-line') { - // reading till the end of the line - while (token === SyntaxKind.WhitespaceTrivia || token === SyntaxKind.NewLineTrivia) { - token = scanner.scan(); - } + if (!isMultiline) { + break; + } - // ignore next line - while (token !== SyntaxKind.NewLineTrivia && token !== SyntaxKind.EndOfFileToken) { - token = scanner.scan(); - } + const end = scanner.getStartPos(); + const comment = contents.substring(start, end); + // preserve multi-line comments which import types + for (const match of comment.matchAll(TYPE_IMPORT_COMMENT_RE)) { + statements.push(match[1]); } break; } diff --git a/packages/kbn-import-resolver/src/__fixtures__/packages/box/kibana.jsonc b/packages/kbn-import-resolver/src/__fixtures__/packages/box/kibana.jsonc new file mode 100644 index 00000000000000..77c5dd64e45b5c --- /dev/null +++ b/packages/kbn-import-resolver/src/__fixtures__/packages/box/kibana.jsonc @@ -0,0 +1,7 @@ +{ + "id": "@kbn/box", + "type": "shared-common", + "description": "test fixture", + "devOnly": false, + "owner": "@elastic/kibana-operations", +} diff --git a/packages/kbn-import-resolver/src/__fixtures__/src/bar/kibana.jsonc b/packages/kbn-import-resolver/src/__fixtures__/src/bar/kibana.jsonc new file mode 100644 index 00000000000000..c60de4be4ba433 --- /dev/null +++ b/packages/kbn-import-resolver/src/__fixtures__/src/bar/kibana.jsonc @@ -0,0 +1,7 @@ +{ + "id": "@kbn/bar", + "type": "shared-common", + "description": "test fixture", + "devOnly": false, + "owner": "@elastic/kibana-operations", +} diff --git a/packages/kbn-import-resolver/src/import_resolver.ts b/packages/kbn-import-resolver/src/import_resolver.ts index 222350bebb38c1..2ad76e9b5a7eb7 100644 --- a/packages/kbn-import-resolver/src/import_resolver.ts +++ b/packages/kbn-import-resolver/src/import_resolver.ts @@ -7,12 +7,10 @@ */ import Path from 'path'; -import Fs from 'fs'; import Resolve from 'resolve'; -import { readPackageManifest, type KibanaPackageManifest } from '@kbn/repo-packages'; import { REPO_ROOT } from '@kbn/repo-info'; -import { readPackageMap, PackageMap } from '@kbn/repo-packages'; +import { getPackages, type Package } from '@kbn/repo-packages'; import { safeStat, readFileSync } from './helpers/fs'; import { ResolveResult } from './resolve_result'; @@ -22,21 +20,8 @@ import { memoize } from './helpers/memoize'; const NODE_MODULE_SEG = Path.sep + 'node_modules' + Path.sep; export class ImportResolver { - static create(repoRoot: string) { - const pkgMap = readPackageMap(); - - const manifests = new Map( - Array.from(pkgMap.entries()).flatMap(([id, repoRelPath]) => { - const manifestPath = Path.resolve(repoRoot, repoRelPath, 'kibana.jsonc'); - if (!Fs.existsSync(manifestPath)) { - return []; - } - - return [[id, readPackageManifest(manifestPath)] as const]; - }) - ); - - return new ImportResolver(repoRoot, pkgMap, manifests); + static create(repoRoot: string, packages: Package[] = getPackages(repoRoot)) { + return new ImportResolver(repoRoot, new Map(packages.map((p) => [p.id, p]))); } private safeStat = memoize(safeStat); @@ -69,32 +54,33 @@ export class ImportResolver { * Map of package ids to normalized root-relative directories * for each package */ - private readonly pkgMap: PackageMap, - /** - * Map of package ids to pkg manifests, if there is no manifest it is - * assumed to be a legacy plugin - */ - private readonly pkgManifests: Map + private readonly pkgsById: Map ) { - // invert the pkgMap, we will update this map with new results as we determine them. - this._dirToPkgId = new Map(Array.from(this.pkgMap).map(([k, v]) => [v, k])); + this._dirToPkg = new Map( + Array.from(this.pkgsById.values()).map((p) => [p.normalizedRepoRelativeDir, p]) + ); } - private readonly _dirToPkgId: Map; - private pkgIdForDir(dir: string): string | null { - const cached = this._dirToPkgId.get(dir); + /** + * map of repoRels and the packages they point to or are contained within. + * This map is initially populated with the position of the packages, and + * from then on serves as a cache for `pkgForDir(dir)` + */ + private readonly _dirToPkg: Map; + private pkgForDir(dir: string): Package | null { + const cached = this._dirToPkg.get(dir); if (cached !== undefined) { return cached; } const parent = Path.dirname(dir); if (parent === '.') { - this._dirToPkgId.set(dir, null); + this._dirToPkg.set(dir, null); return null; } - const pkgId = this.pkgIdForDir(parent); - this._dirToPkgId.set(dir, pkgId); + const pkgId = this.pkgForDir(parent); + this._dirToPkg.set(dir, pkgId); return pkgId; } @@ -104,7 +90,7 @@ export class ImportResolver { return null; } - return this.pkgIdForDir(Path.dirname(relative)); + return this.pkgForDir(Path.dirname(relative))?.id ?? null; } getPackageManifestForPath(path: string) { @@ -113,20 +99,15 @@ export class ImportResolver { } getAbsolutePackageDir(pkgId: string) { - const dir = this.pkgMap.get(pkgId); - return dir ? Path.resolve(this.cwd, dir) : null; + return this.pkgsById.get(pkgId)?.directory ?? null; } - /** - * Is the package a bazel package? - * @deprecated - */ - isBazelPackage(pkgId: string) { - return !!this.getPkgManifest(pkgId); + isRepoPkg(pkgId: string) { + return this.pkgsById.has(pkgId); } getPkgManifest(pkgId: string) { - return this.pkgManifests.get(pkgId); + return this.pkgsById.get(pkgId)?.manifest; } private shouldIgnore(req: string): boolean { @@ -274,17 +255,15 @@ export class ImportResolver { if (req[0] !== '.') { const parts = req.split('/'); const pkgId = parts[0].startsWith('@') ? `${parts[0]}/${parts[1]}` : `${parts[0]}`; - if (this.pkgMap.has(pkgId)) { - const pkgDir = this.getAbsolutePackageDir(pkgId); - if (pkgDir) { - return this.resolve( - `./${Path.relative( - dirname, - parts.length > 2 ? Path.resolve(pkgDir, ...parts.slice(2)) : pkgDir - )}`, - dirname - ); - } + const pkgDir = this.getAbsolutePackageDir(pkgId); + if (pkgDir) { + return this.resolve( + `./${Path.relative( + dirname, + parts.length > 2 ? Path.resolve(pkgDir, ...parts.slice(2)) : pkgDir + )}`, + dirname + ); } } diff --git a/packages/kbn-import-resolver/src/integration_tests/import_resolver.test.ts b/packages/kbn-import-resolver/src/integration_tests/import_resolver.test.ts index 4fbedcaf385109..e26e37e1495e58 100644 --- a/packages/kbn-import-resolver/src/integration_tests/import_resolver.test.ts +++ b/packages/kbn-import-resolver/src/integration_tests/import_resolver.test.ts @@ -7,6 +7,7 @@ */ import Path from 'path'; +import { Package } from '@kbn/repo-packages'; import { createAbsolutePathSerializer } from '@kbn/jest-serializers'; import { ImportResolver } from '../import_resolver'; @@ -15,40 +16,17 @@ const FIXTURES_DIR = Path.resolve(__dirname, '../__fixtures__'); expect.addSnapshotSerializer(createAbsolutePathSerializer()); -const resolver = new ImportResolver( - FIXTURES_DIR, - new Map([ - ['@synth/bar', 'src/bar'], - ['@pkg/box', 'packages/box'], - ]), - new Map([ - [ - '@pkg/box', - { - id: '@pkg/box', - type: 'shared-common', - owner: [], - }, - ], - ]) -); +const resolver = ImportResolver.create(FIXTURES_DIR, [ + Package.fromManifest(FIXTURES_DIR, Path.resolve(FIXTURES_DIR, 'packages/box/kibana.jsonc')), + Package.fromManifest(FIXTURES_DIR, Path.resolve(FIXTURES_DIR, 'src/bar/kibana.jsonc')), +]); describe('#resolve()', () => { - it('resolves imports to synth packages', () => { - expect(resolver.resolve('@synth/bar', FIXTURES_DIR)).toMatchInlineSnapshot(` - Object { - "absolute": /packages/kbn-import-resolver/src/__fixtures__/src/bar/index.js, - "pkgId": "@synth/bar", - "type": "file", - } - `); - }); - - it('resolves imports to bazel packages', () => { - expect(resolver.resolve('@pkg/box', FIXTURES_DIR)).toMatchInlineSnapshot(` + it('resolves imports to packages', () => { + expect(resolver.resolve('@kbn/box', FIXTURES_DIR)).toMatchInlineSnapshot(` Object { "absolute": /packages/kbn-import-resolver/src/__fixtures__/packages/box/index.js, - "pkgId": "@pkg/box", + "pkgId": "@kbn/box", "type": "file", } `); @@ -77,7 +55,7 @@ describe('#resolve()', () => { expect(resolver.resolve('./bar', Path.resolve(FIXTURES_DIR, 'src/bar'))).toMatchInlineSnapshot(` Object { "absolute": /packages/kbn-import-resolver/src/__fixtures__/src/bar/bar.js, - "pkgId": "@synth/bar", + "pkgId": "@kbn/bar", "type": "file", } `); @@ -131,16 +109,14 @@ describe('#resolve()', () => { }); describe('#getPackageIdForPath()', () => { - it('returns package id for bazel package', () => { + it('returns package id for package', () => { expect( resolver.getPackageIdForPath(Path.resolve(FIXTURES_DIR, 'packages/box/index.js')) - ).toMatchInlineSnapshot(`"@pkg/box"`); - }); + ).toMatchInlineSnapshot(`"@kbn/box"`); - it('returns package id for synth package', () => { expect( resolver.getPackageIdForPath(Path.resolve(FIXTURES_DIR, 'src/bar/index.js')) - ).toMatchInlineSnapshot(`"@synth/bar"`); + ).toMatchInlineSnapshot(`"@kbn/bar"`); }); it('returns null for files outside of a package', () => { @@ -151,13 +127,11 @@ describe('#getPackageIdForPath()', () => { }); describe('#getAbsolutePackageDir()', () => { - it('returns path for bazel package', () => { - expect(resolver.getAbsolutePackageDir('@pkg/box')).toMatchInlineSnapshot( + it('returns path for package', () => { + expect(resolver.getAbsolutePackageDir('@kbn/box')).toMatchInlineSnapshot( `/packages/kbn-import-resolver/src/__fixtures__/packages/box` ); - }); - it('returns path for synth package', () => { - expect(resolver.getAbsolutePackageDir('@synth/bar')).toMatchInlineSnapshot( + expect(resolver.getAbsolutePackageDir('@kbn/bar')).toMatchInlineSnapshot( `/packages/kbn-import-resolver/src/__fixtures__/src/bar` ); }); @@ -168,18 +142,3 @@ describe('#getAbsolutePackageDir()', () => { expect(resolver.getAbsolutePackageDir('@kbn/invalid')).toMatchInlineSnapshot(`null`); }); }); - -describe('#isBazelPackage()', () => { - it('returns true for bazel packages', () => { - expect(resolver.isBazelPackage('@pkg/box')).toBe(true); - }); - it('returns false for synth packages', () => { - expect(resolver.isBazelPackage('@synth/bar')).toBe(false); - }); - it('returns false for node_modules packages', () => { - expect(resolver.isBazelPackage('foo')).toBe(false); - }); - it('returns false for unknown packages', () => { - expect(resolver.isBazelPackage('@kbn/invalid')).toBe(false); - }); -}); diff --git a/packages/kbn-json-ast/README.md b/packages/kbn-json-ast/README.md index b45ab336280781..81752d81e85310 100644 --- a/packages/kbn-json-ast/README.md +++ b/packages/kbn-json-ast/README.md @@ -1,3 +1,3 @@ # @kbn/json-ast -Tools for parsing and mutating JSON files without rewriting the whole file. JSON-C is also supported so that we can update kibana.jsonc and tsconfig.json files. \ No newline at end of file +Tools for parsing and mutating JSON files without rewriting the whole file. JSON-C is also supported so that we can update kibana.jsonc and tsconfig.json files. diff --git a/packages/kbn-json-ast/index.ts b/packages/kbn-json-ast/index.ts index 9999b1a0c4e57a..e0fab5a3fd4f7c 100644 --- a/packages/kbn-json-ast/index.ts +++ b/packages/kbn-json-ast/index.ts @@ -14,5 +14,5 @@ export { removeAllReferences, } from './src/references'; export { setExtends } from './src/extends'; -export { setProp } from './src/props'; +export { setProp, getPropFromSource as getProp, removeProp } from './src/props'; export { snip } from './src/snip'; diff --git a/packages/kbn-json-ast/src/ends.ts b/packages/kbn-json-ast/src/ends.ts index 4d0d23076fc74f..220612463cd8b3 100644 --- a/packages/kbn-json-ast/src/ends.ts +++ b/packages/kbn-json-ast/src/ends.ts @@ -8,6 +8,9 @@ import * as T from '@babel/types'; +/** + * Determine the start and end position of the given node + */ export function getEnds(node: T.Node): [number, number] { const { start, end } = node; if (start == null || end == null) { @@ -16,13 +19,16 @@ export function getEnds(node: T.Node): [number, number] { return [start, end]; } +/** + * Get the ends of the node, and then expand them to include all the leading whitespace or newlines, and any trailing commas or whitespace + */ export function getExpandedEnds(source: string, node: T.Node): [number, number] { let [start, end] = getEnds(node); while (source[start - 1] === ' ' || source[start - 1] === '\n') { start -= 1; } - while (source[end] === ',') { + while (source[end] === ' ' || source[end] === ',') { end += 1; } diff --git a/packages/kbn-json-ast/src/props.ts b/packages/kbn-json-ast/src/props.ts index 6fcd136d196b98..767b986b82d631 100644 --- a/packages/kbn-json-ast/src/props.ts +++ b/packages/kbn-json-ast/src/props.ts @@ -10,7 +10,7 @@ import { getAst } from './ast'; import { stringify, redentJson } from './json'; import { snip } from './snip'; import { T } from './babel'; -import { getEnds } from './ends'; +import { getEnds, getExpandedEnds } from './ends'; export function getProp(obj: T.ObjectExpression, name: string) { return obj.properties.find((p): p is T.ObjectProperty & { key: T.StringLiteral } => { @@ -26,6 +26,28 @@ export function getEndOfLastProp(obj: T.ObjectExpression) { return obj.properties.reduce((acc, prop) => Math.max(acc, getEnds(prop)[1]), 0); } +/** + * Removes a property from a JSONc object. If the property does not exist the source is just returned + */ +export function removeProp( + /** the jsonc to modify */ + source: string, + /** The key to set */ + key: string, + /** extra key-value options */ + opts?: { + node?: T.ObjectExpression; + } +) { + const ast = opts?.node ?? getAst(source); + const prop = getProp(ast, key); + if (!prop) { + return source; + } + + return snip(source, [getExpandedEnds(source, prop)]); +} + export function setProp( /** the jsonc to modify */ source: string, @@ -33,9 +55,12 @@ export function setProp( key: string, /** the value of the key */ value: any, + /** extra key-value options */ opts?: { /** by default, if the key isn't already in the json, it will be added at the bottom. Set this to true to add the key at the top instead */ insertAtTop?: boolean; + /** by default, if the key isn't already in the json, it will be added at the bottom. Set this to an existing property node to have the key added after this node */ + insertAfter?: T.ObjectProperty; /** In order to set the property an object other than the root object, parse the source and pass the node of the desired object here (make sure to also pass spaces) */ node?: T.ObjectExpression; /** This overrides the default " " spacing used for multi line or new properties that are added */ @@ -55,11 +80,15 @@ export function setProp( if (opts?.insertAtTop) { const [start] = getEnds(ast.properties[0]); return snip(source, [[start, start, `${newPropJson},\n${spaces}`]]); + } else { + const insertAt = opts?.insertAfter ? getEnds(opts.insertAfter)[1] : getEndOfLastProp(ast); + return snip(source, [[insertAt, insertAt, `,\n${spaces}${newPropJson}`]]); } - - const endOfLastProp = getEndOfLastProp(ast); - return snip(source, [[endOfLastProp, endOfLastProp, `,\n${spaces}${newPropJson}`]]); } return snip(source, [[...getEnds(prop), newPropJson]]); } + +export function getPropFromSource(source: string, name: string) { + return getProp(getAst(source), name); +} diff --git a/packages/kbn-json-ast/src/references.test.ts b/packages/kbn-json-ast/src/references.test.ts index cd3a071bddac32..a5e780c957522d 100644 --- a/packages/kbn-json-ast/src/references.test.ts +++ b/packages/kbn-json-ast/src/references.test.ts @@ -129,7 +129,7 @@ describe('removeReferences()', () => { ) ).toMatchInlineSnapshot(` "{ - \\"kbn_references\\": [ \\"baz\\"] + \\"kbn_references\\": [\\"baz\\"] }" `); expect( diff --git a/packages/kbn-lint-packages-cli/README.md b/packages/kbn-lint-packages-cli/README.md index 5cb8862015f9d3..72820451685d39 100644 --- a/packages/kbn-lint-packages-cli/README.md +++ b/packages/kbn-lint-packages-cli/README.md @@ -1,3 +1,3 @@ -# @kbn/package-linter-cli +# @kbn/lint-packages-cli -CLI for running the package linter, which just validates a couple rules for each package. \ No newline at end of file +CLI for running the package linter, which just validates a couple rules for each package. diff --git a/packages/kbn-lint-packages-cli/migrate_plugins_to_package.ts b/packages/kbn-lint-packages-cli/migrate_plugins_to_package.ts new file mode 100644 index 00000000000000..de8e7d8e922da6 --- /dev/null +++ b/packages/kbn-lint-packages-cli/migrate_plugins_to_package.ts @@ -0,0 +1,151 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import Fs from 'fs'; +import Fsp from 'fs/promises'; +import Path from 'path'; + +import { REPO_ROOT } from '@kbn/repo-info'; +import { asyncMapWithLimit } from '@kbn/std'; +import { RepoPath } from '@kbn/repo-path'; +import { type PluginPackageManifest } from '@kbn/repo-packages'; + +function isObj(v: unknown): v is Record { + return typeof v === 'object' && v !== null; +} + +function convertPluginIdToPackageId(pluginId: string) { + if (pluginId === 'core') { + // core is the only non-plugin + return `@kbn/core`; + } + + return `@kbn/${pluginId + .split('') + .flatMap((c) => (c.toUpperCase() === c ? `-${c.toLowerCase()}` : c)) + .join('')}-plugin` + .replace(/-\w(-\w)+-/g, (match) => `-${match.split('-').join('')}-`) + .replace(/-plugin-plugin$/, '-plugin'); +} + +function normalizeDir(dir: string): string { + if (dir.startsWith('./')) { + return normalizeDir(dir.slice(2)); + } + + if (dir !== '/' && dir.endsWith('/')) { + return normalizeDir(dir.slice(0, -1)); + } + + if (!dir.startsWith('/')) { + return normalizeDir(`/${dir}`); + } + + return dir; +} + +function getPluginManifest(dir: string, legacy: any, owners: string[]): PluginPackageManifest { + return { + type: 'plugin', + id: convertPluginIdToPackageId(legacy.id), + owner: owners, + description: legacy.description || undefined, + serviceFolders: legacy.serviceFolders, + plugin: { + id: legacy.id, + type: legacy.type, + server: legacy.server ?? Fs.existsSync(Path.resolve(dir, 'server')), + browser: legacy.ui ?? Fs.existsSync(Path.resolve(dir, 'public')), + configPath: legacy.configPath, + enabledOnAnonymousPages: legacy.enabledOnAnonymousPages, + requiredPlugins: legacy.requiredPlugins?.length ? legacy.requiredPlugins : undefined, + optionalPlugins: legacy.optionalPlugins?.length ? legacy.optionalPlugins : undefined, + requiredBundles: legacy.requiredBundles?.length ? legacy.requiredBundles : undefined, + extraPublicDirs: legacy.extraPublicDirs?.length ? legacy.extraPublicDirs : undefined, + }, + }; +} + +export async function migratePluginsToPackages(legacyManifests: RepoPath[]) { + const CODEOWNERS = new Map( + (await Fsp.readFile(Path.resolve(REPO_ROOT, '.github/CODEOWNERS'), 'utf8')) + .split('\n') + .flatMap((line) => { + const trim = line.trim(); + if (!trim || trim.startsWith('#')) { + return []; + } + + const [dir, ...pings] = trim.split('@'); + return [ + [ + normalizeDir(dir.trim()), + ['', ...pings] + .join('@') + .split(' ') + .map((h) => h.trim()), + ], + ]; + }) + ); + + function getCodeowners(dir: string): string[] | null { + const codeowner = CODEOWNERS.get(normalizeDir(dir)); + if (!codeowner) { + const parent = Path.dirname(dir); + if (parent !== dir) { + return getCodeowners(parent); + } else { + return null; + } + } + + return codeowner; + } + + const rewrites: Array<[old: string, path: string, content: string]> = []; + for (const legacy of legacyManifests) { + const json = JSON.parse(Fs.readFileSync(legacy.abs, 'utf8')); + const dir = Path.dirname(legacy.abs); + const repoRelDir = Path.dirname(legacy.repoRel); + const codeowners = getCodeowners(repoRelDir); + + const owners = + codeowners ?? + (isObj(json.owner) && json.owner.githubTeam + ? [ + `@elastic/${ + json.owner.githubTeam.startsWith('@elastic/') + ? json.owner.githubTeam.slice(9) + : json.owner.githubTeam + }`, + ] + : undefined); + + if (!owners) { + throw new Error(`unable to determine owner for ${legacy.repoRel}`); + } + + const manifest = getPluginManifest(dir, json, owners); + if (manifest.owner.length === 1) { + // unwrap arrays for single owners + (manifest as any).owner = manifest.owner[0]; + } + + rewrites.push([ + Path.resolve(dir, 'kibana.json'), + Path.resolve(dir, 'kibana.jsonc'), + JSON.stringify(manifest, null, 2) + '\n', + ]); + } + + await asyncMapWithLimit(rewrites, 30, async ([old, path, content]) => { + await Fsp.unlink(old); + await Fsp.writeFile(path, content); + }); +} diff --git a/packages/kbn-lint-packages-cli/rules/constant_version.ts b/packages/kbn-lint-packages-cli/rules/constant_version.ts new file mode 100644 index 00000000000000..02f070d624a6db --- /dev/null +++ b/packages/kbn-lint-packages-cli/rules/constant_version.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { PackageRule } from '@kbn/repo-linter'; +import { setProp, getProp } from '@kbn/json-ast'; + +export const constantVersionRule = PackageRule.create('constantVersion', { + async check({ pkg }) { + if (pkg.pkg && pkg.pkg.version !== '1.0.0') { + this.err('The "version" in the package.json file must be "1.0.0"', { + 'package.json': (source) => + setProp(source, 'version', '1.0.0', { + insertAfter: getProp(source, 'name'), + }), + }); + } + }, +}); diff --git a/packages/kbn-lint-packages-cli/rules/index.ts b/packages/kbn-lint-packages-cli/rules/index.ts index 65f16c71d74f38..0a72ca66543d4b 100644 --- a/packages/kbn-lint-packages-cli/rules/index.ts +++ b/packages/kbn-lint-packages-cli/rules/index.ts @@ -9,6 +9,13 @@ import type { PackageRule } from '@kbn/repo-linter'; import { matchingPackageNameRule } from './matching_package_name'; +import { constantVersionRule } from './constant_version'; +import { noLicenseRule } from './no_license'; import { noBasenameCollisionsRule } from './no_basename_collisions'; -export const RULES: PackageRule[] = [matchingPackageNameRule, noBasenameCollisionsRule]; +export const RULES: PackageRule[] = [ + matchingPackageNameRule, + constantVersionRule, + noLicenseRule, + noBasenameCollisionsRule, +]; diff --git a/packages/kbn-lint-packages-cli/rules/no_license.ts b/packages/kbn-lint-packages-cli/rules/no_license.ts new file mode 100644 index 00000000000000..3b28faca0914fa --- /dev/null +++ b/packages/kbn-lint-packages-cli/rules/no_license.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { PackageRule } from '@kbn/repo-linter'; +import { setProp, getProp } from '@kbn/json-ast'; + +const DEFAULT_LICENSE = 'SSPL-1.0 OR Elastic License 2.0'; +const XPACK_LICENSE = 'Elastic License 2.0'; +const LICENSE_EXCEPTIONS = Object.entries({ + MIT: ['@kbn/safer-lodash-set', '@kbn/handlebars', '@kbn/expect'], +}); + +export const noLicenseRule = PackageRule.create('noLicense', { + async check({ pkg }) { + if (!pkg.pkg) { + return; + } + + const exception = LICENSE_EXCEPTIONS.find(([, pkgIds]) => pkgIds.includes(pkg.id)); + const expected = exception + ? exception[0] + : pkg.normalizedRepoRelativeDir.startsWith('x-pack') + ? XPACK_LICENSE + : DEFAULT_LICENSE; + + if (pkg.pkg.license !== expected) { + this.err(`The "license" in the package.json file must be ${expected}`, { + 'package.json': (source) => + setProp(source, 'license', expected, { + insertAfter: getProp(source, 'version') ?? getProp(source, 'name'), + }), + }); + } + }, +}); diff --git a/packages/kbn-lint-packages-cli/run_lint_packages_cli.ts b/packages/kbn-lint-packages-cli/run_lint_packages_cli.ts index a8f49992e74105..73e5f3c469fcd9 100644 --- a/packages/kbn-lint-packages-cli/run_lint_packages_cli.ts +++ b/packages/kbn-lint-packages-cli/run_lint_packages_cli.ts @@ -12,12 +12,16 @@ import { run } from '@kbn/dev-cli-runner'; import { createFailError } from '@kbn/dev-cli-errors'; import { getRepoFiles } from '@kbn/get-repo-files'; import { PackageFileMap } from '@kbn/repo-file-maps'; -import { getPackages } from '@kbn/repo-packages'; +import { updatePackageMap, getPackages } from '@kbn/repo-packages'; import { REPO_ROOT } from '@kbn/repo-info'; import { TS_PROJECTS } from '@kbn/ts-projects'; +import { makeMatcher } from '@kbn/picomatcher'; import { runLintRules, PackageLintTarget } from '@kbn/repo-linter'; import { RULES } from './rules'; +import { migratePluginsToPackages } from './migrate_plugins_to_package'; + +const legacyManifestMatcher = makeMatcher(['**/kibana.json', '!**/{__fixtures__,fixtures}/**']); const kebabCase = (input: string) => input @@ -40,7 +44,26 @@ function getFilter(input: string) { run( async ({ log, flagsReader }) => { const filter = flagsReader.getPositionals(); + let allRepoFiles = await getRepoFiles(); + + const legacyPackageManifests = Array.from(allRepoFiles).filter((f) => + legacyManifestMatcher(f.repoRel) + ); + + if (legacyPackageManifests.length) { + await migratePluginsToPackages(legacyPackageManifests); + log.warning('Migrated legacy plugins to packages'); + allRepoFiles = await getRepoFiles(); + } + + const pkgManifestPaths = Array.from(allRepoFiles) + .filter((f) => f.basename === 'kibana.jsonc') + .map((f) => f.abs); + if (await updatePackageMap(REPO_ROOT, pkgManifestPaths)) { + log.warning('updated package map'); + } const packages = getPackages(REPO_ROOT); + const allTargets = packages .map( (p) => @@ -69,7 +92,7 @@ run( ) ).sort((a, b) => a.repoRel.localeCompare(b.repoRel)); - const fileMap = new PackageFileMap(packages, await getRepoFiles()); + const fileMap = new PackageFileMap(packages, allRepoFiles); const { lintingErrorCount } = await runLintRules(log, toLint, RULES, { fix: flagsReader.boolean('fix'), getFiles: (target) => fileMap.getFiles(target.pkg), diff --git a/packages/kbn-lint-packages-cli/tsconfig.json b/packages/kbn-lint-packages-cli/tsconfig.json index 44df028e66ef57..9370d19eecc1f9 100644 --- a/packages/kbn-lint-packages-cli/tsconfig.json +++ b/packages/kbn-lint-packages-cli/tsconfig.json @@ -24,5 +24,8 @@ "@kbn/repo-file-maps", "@kbn/json-ast", "@kbn/set-map", + "@kbn/std", + "@kbn/repo-path", + "@kbn/picomatcher", ] } diff --git a/packages/kbn-lint-ts-projects-cli/run_lint_ts_projects_cli.ts b/packages/kbn-lint-ts-projects-cli/run_lint_ts_projects_cli.ts index 50ed41b7e66a3a..370f27a904bfc0 100644 --- a/packages/kbn-lint-ts-projects-cli/run_lint_ts_projects_cli.ts +++ b/packages/kbn-lint-ts-projects-cli/run_lint_ts_projects_cli.ts @@ -31,9 +31,9 @@ function getFilter(input: string) { tsProject.path === abs || tsProject.directory === abs || abs.startsWith(tsProject.directory + '/') || - tsProject.pkgInfo?.repoRel === input || - (tsProject.pkgInfo && Path.resolve(REPO_ROOT, tsProject.pkgInfo.repoRel) === abs) || - (tsProject.pkgInfo && abs.startsWith(Path.resolve(REPO_ROOT, tsProject.pkgInfo.repoRel) + '/')); + tsProject.pkg?.normalizedRepoRelativeDir === input || + tsProject.pkg?.directory === abs || + (tsProject.pkg && abs.startsWith(tsProject.pkg.directory + '/')); } function validateProjectOwnership( diff --git a/packages/kbn-optimizer/README.mdx b/packages/kbn-optimizer/README.mdx index f092c4cc5ffba0..1aee759145999a 100644 --- a/packages/kbn-optimizer/README.mdx +++ b/packages/kbn-optimizer/README.mdx @@ -84,7 +84,6 @@ const log = new ToolingLog({ const config = OptimizerConfig.create({ repoRoot: Path.resolve(__dirname, '../../..'), watch: false, - oss: true, dist: true }); diff --git a/packages/kbn-optimizer/jest.integration.config.js b/packages/kbn-optimizer/jest.integration.config.js deleted file mode 100644 index a51f4c588af8c1..00000000000000 --- a/packages/kbn-optimizer/jest.integration.config.js +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -module.exports = { - preset: '@kbn/test/jest_integration_node', - rootDir: '../..', - roots: ['/packages/kbn-optimizer'], -}; diff --git a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar/kibana.json b/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar/kibana.json deleted file mode 100644 index 0aadeb1644fe8e..00000000000000 --- a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar/kibana.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "id": "bar", - "ui": true, - "requiredBundles": ["foo"], - "version": "8.0.0", - "owner": { - "name": "Operations" - } -} diff --git a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar/public/index.scss b/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar/public/index.scss deleted file mode 100644 index 9603185daf4109..00000000000000 --- a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar/public/index.scss +++ /dev/null @@ -1,4 +0,0 @@ -body { - /* stylelint-disable-next-line color-named */ - color: green; -} diff --git a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar/public/index.ts b/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar/public/index.ts deleted file mode 100644 index c58c57ba65785c..00000000000000 --- a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar/public/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import './legacy/styles.scss'; -import './index.scss'; -import { fooLibFn } from '../../foo/public'; -export * from './lib'; -export { fooLibFn }; diff --git a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar/public/legacy/_other_styles.scss b/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar/public/legacy/_other_styles.scss deleted file mode 100644 index fae6b1e6276617..00000000000000 --- a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar/public/legacy/_other_styles.scss +++ /dev/null @@ -1,4 +0,0 @@ -p { - /* stylelint-disable-next-line color-named */ - background-color: rebeccapurple; -} diff --git a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar/public/legacy/styles.scss b/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar/public/legacy/styles.scss deleted file mode 100644 index 89c5d0d7d98c1f..00000000000000 --- a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar/public/legacy/styles.scss +++ /dev/null @@ -1,5 +0,0 @@ -@import './other_styles.scss'; - -body { - width: $globalStyleConstant; -} diff --git a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar/public/lib.ts b/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar/public/lib.ts deleted file mode 100644 index a122b782c905be..00000000000000 --- a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar/public/lib.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export function barLibFn() { - return 'bar'; -} diff --git a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/foo/kibana.json b/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/foo/kibana.json deleted file mode 100644 index ceea6483ab47a0..00000000000000 --- a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/foo/kibana.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "id": "foo", - "owner": { - "name": "Operations" - }, - "ui": true, - "version": "8.0.0" -} diff --git a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/foo/public/async_import.ts b/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/foo/public/async_import.ts deleted file mode 100644 index 12f54ee9ba6e94..00000000000000 --- a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/foo/public/async_import.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export function foo() {} diff --git a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/foo/public/ext.ts b/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/foo/public/ext.ts deleted file mode 100644 index 25a25a9d1d10b0..00000000000000 --- a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/foo/public/ext.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export const ext = 'TRUE'; diff --git a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/foo/public/index.ts b/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/foo/public/index.ts deleted file mode 100644 index d28a51fe45deec..00000000000000 --- a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/foo/public/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export * from './lib'; -export * from './ext'; - -export async function getFoo() { - return await import('./async_import'); -} diff --git a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/foo/public/lib.ts b/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/foo/public/lib.ts deleted file mode 100644 index d25d1deb919d5f..00000000000000 --- a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/foo/public/lib.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export function fooLibFn() { - return 'foo'; -} diff --git a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/nested/baz/kibana.json b/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/nested/baz/kibana.json deleted file mode 100644 index f8b1bf6bcc39ae..00000000000000 --- a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/nested/baz/kibana.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "id": "baz", - "owner": { - "name": "Operations" - }, - "version": "8.0.0" -} diff --git a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/nested/baz/server/index.ts b/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/nested/baz/server/index.ts deleted file mode 100644 index d4dcaa77cc47ad..00000000000000 --- a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/nested/baz/server/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export * from './lib'; diff --git a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/nested/baz/server/lib.ts b/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/nested/baz/server/lib.ts deleted file mode 100644 index 9196253a97b390..00000000000000 --- a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/nested/baz/server/lib.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export function bazLibFn() { - return 'baz'; -} diff --git a/packages/kbn-optimizer/src/__fixtures__/mock_repo/src/core/public/styles/core_app/_globals_v7dark.scss b/packages/kbn-optimizer/src/__fixtures__/mock_repo/src/core/public/styles/core_app/_globals_v7dark.scss deleted file mode 100644 index 83995ca65211bd..00000000000000 --- a/packages/kbn-optimizer/src/__fixtures__/mock_repo/src/core/public/styles/core_app/_globals_v7dark.scss +++ /dev/null @@ -1 +0,0 @@ -$globalStyleConstant: 10; diff --git a/packages/kbn-optimizer/src/__fixtures__/mock_repo/src/core/public/styles/core_app/_globals_v7light.scss b/packages/kbn-optimizer/src/__fixtures__/mock_repo/src/core/public/styles/core_app/_globals_v7light.scss deleted file mode 100644 index 63beb9927b9f57..00000000000000 --- a/packages/kbn-optimizer/src/__fixtures__/mock_repo/src/core/public/styles/core_app/_globals_v7light.scss +++ /dev/null @@ -1 +0,0 @@ -$globalStyleConstant: 11; diff --git a/packages/kbn-optimizer/src/__fixtures__/mock_repo/src/core/public/styles/core_app/_globals_v8dark.scss b/packages/kbn-optimizer/src/__fixtures__/mock_repo/src/core/public/styles/core_app/_globals_v8dark.scss deleted file mode 100644 index 4040cab1878fc6..00000000000000 --- a/packages/kbn-optimizer/src/__fixtures__/mock_repo/src/core/public/styles/core_app/_globals_v8dark.scss +++ /dev/null @@ -1 +0,0 @@ -$globalStyleConstant: 12; diff --git a/packages/kbn-optimizer/src/__fixtures__/mock_repo/src/core/public/styles/core_app/_globals_v8light.scss b/packages/kbn-optimizer/src/__fixtures__/mock_repo/src/core/public/styles/core_app/_globals_v8light.scss deleted file mode 100644 index 3918413c068639..00000000000000 --- a/packages/kbn-optimizer/src/__fixtures__/mock_repo/src/core/public/styles/core_app/_globals_v8light.scss +++ /dev/null @@ -1 +0,0 @@ -$globalStyleConstant: 13; diff --git a/packages/kbn-optimizer/src/__fixtures__/mock_repo/test_plugins/test_baz/kibana.json b/packages/kbn-optimizer/src/__fixtures__/mock_repo/test_plugins/test_baz/kibana.json deleted file mode 100644 index e784007bce6d8c..00000000000000 --- a/packages/kbn-optimizer/src/__fixtures__/mock_repo/test_plugins/test_baz/kibana.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "id": "test_baz", - "owner": { - "name": "Operations" - }, - "version": "8.0.0" -} diff --git a/packages/kbn-optimizer/src/__fixtures__/mock_repo/test_plugins/test_baz/server/index.ts b/packages/kbn-optimizer/src/__fixtures__/mock_repo/test_plugins/test_baz/server/index.ts deleted file mode 100644 index d4dcaa77cc47ad..00000000000000 --- a/packages/kbn-optimizer/src/__fixtures__/mock_repo/test_plugins/test_baz/server/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export * from './lib'; diff --git a/packages/kbn-optimizer/src/__fixtures__/mock_repo/test_plugins/test_baz/server/lib.ts b/packages/kbn-optimizer/src/__fixtures__/mock_repo/test_plugins/test_baz/server/lib.ts deleted file mode 100644 index 9196253a97b390..00000000000000 --- a/packages/kbn-optimizer/src/__fixtures__/mock_repo/test_plugins/test_baz/server/lib.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export function bazLibFn() { - return 'baz'; -} diff --git a/packages/kbn-optimizer/src/__fixtures__/mock_repo/x-pack/baz/kibana.json b/packages/kbn-optimizer/src/__fixtures__/mock_repo/x-pack/baz/kibana.json deleted file mode 100644 index d94123ae7ef023..00000000000000 --- a/packages/kbn-optimizer/src/__fixtures__/mock_repo/x-pack/baz/kibana.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "id": "baz", - "owner": { "name": "Operations", "githubTeam": "kibana-operations" }, - "ui": true, - "version": "8.0.0" -} diff --git a/packages/kbn-optimizer/src/__fixtures__/mock_repo/x-pack/baz/public/index.ts b/packages/kbn-optimizer/src/__fixtures__/mock_repo/x-pack/baz/public/index.ts deleted file mode 100644 index 2ba46198db497e..00000000000000 --- a/packages/kbn-optimizer/src/__fixtures__/mock_repo/x-pack/baz/public/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -// eslint-disable-next-line no-console -console.log('plugin in an x-pack dir'); diff --git a/packages/kbn-optimizer/src/cli.ts b/packages/kbn-optimizer/src/cli.ts index b0732e931e04f8..e58973ecb9640c 100644 --- a/packages/kbn-optimizer/src/cli.ts +++ b/packages/kbn-optimizer/src/cli.ts @@ -92,13 +92,6 @@ export function runKbnOptimizerCli(options: { defaultLimitsPath: string }) { throw createFlagError('expected --workers to be a number greater than 0'); } - const extraPluginScanDirs = ([] as string[]) - .concat((flags['scan-dir'] as string | string[]) || []) - .map((p) => Path.resolve(p)); - if (!extraPluginScanDirs.every((s) => typeof s === 'string')) { - throw createFlagError('expected --scan-dir to be a string'); - } - const reportStats = flags['report-stats'] ?? false; if (typeof reportStats !== 'boolean') { throw createFlagError('expected --report-stats to have no value'); @@ -135,13 +128,11 @@ export function runKbnOptimizerCli(options: { defaultLimitsPath: string }) { repoRoot: REPO_ROOT, watch, maxWorkerCount, - oss: oss && !(validateLimits || updateLimits), dist: dist || updateLimits, cache, examples: examples && !(validateLimits || updateLimits), testPlugins: testPlugins && !(validateLimits || updateLimits), profileWebpack, - extraPluginScanDirs, inspectWorkers, includeCoreBundle, filter, diff --git a/packages/kbn-optimizer/src/common/bundle.test.ts b/packages/kbn-optimizer/src/common/bundle.test.ts index 5989489bb81ba3..59d730dd661be8 100644 --- a/packages/kbn-optimizer/src/common/bundle.test.ts +++ b/packages/kbn-optimizer/src/common/bundle.test.ts @@ -14,11 +14,15 @@ jest.mock('fs'); const SPEC: BundleSpec = { contextDir: '/foo/bar', - publicDirNames: ['public'], id: 'bar', outputDir: '/foo/bar/target', sourceRoot: '/foo', type: 'plugin', + remoteInfo: { + pkgId: '@kbn/foo-bundle', + targets: ['public'], + }, + ignoreMetrics: false, }; it('creates cache keys', () => { @@ -61,11 +65,15 @@ it('creates cache keys', () => { "banner": undefined, "contextDir": "/foo/bar", "id": "bar", + "ignoreMetrics": false, "manifestPath": undefined, "outputDir": "/foo/bar/target", - "publicDirNames": Array [ - "public", - ], + "remoteInfo": Object { + "pkgId": "@kbn/foo-bundle", + "targets": Array [ + "public", + ], + }, "sourceRoot": "/foo", "type": "plugin", }, @@ -98,12 +106,16 @@ it('parses bundles from JSON specs', () => { }, "contextDir": "/foo/bar", "id": "bar", + "ignoreMetrics": false, "manifestPath": undefined, "outputDir": "/foo/bar/target", "pageLoadAssetSizeLimit": undefined, - "publicDirNames": Array [ - "public", - ], + "remoteInfo": Object { + "pkgId": "@kbn/foo-bundle", + "targets": Array [ + "public", + ], + }, "sourceRoot": "/foo", "type": "plugin", }, diff --git a/packages/kbn-optimizer/src/common/bundle.ts b/packages/kbn-optimizer/src/common/bundle.ts index a2f0653d23af75..c7a07928e15241 100644 --- a/packages/kbn-optimizer/src/common/bundle.ts +++ b/packages/kbn-optimizer/src/common/bundle.ts @@ -8,9 +8,10 @@ import Path from 'path'; import Fs from 'fs'; +import { Jsonc } from '@kbn/repo-packages'; import { BundleCache } from './bundle_cache'; -import { UnknownVals } from './ts_helpers'; +import { UnknownVals, isObj } from './ts_helpers'; import { omit } from './obj_helpers'; import { includes } from './array_helpers'; import type { Hashes } from './hashes'; @@ -20,15 +21,13 @@ const VALID_BUNDLE_TYPES = ['plugin' as const, 'entry' as const]; const DEFAULT_IMPLICIT_BUNDLE_DEPS = ['core']; -const isStringArray = (input: any): input is string[] => - Array.isArray(input) && input.every((x) => typeof x === 'string'); +const toStringArray = (input: any): string[] => + Array.isArray(input) && input.every((x) => typeof x === 'string') ? input : []; export interface BundleSpec { readonly type: typeof VALID_BUNDLE_TYPES[0]; /** Unique id for this bundle */ readonly id: string; - /** directory names relative to the contextDir that can be imported from */ - readonly publicDirNames: string[]; /** Absolute path to the plugin source directory */ readonly contextDir: string; /** Absolute path to the root of the repository */ @@ -41,6 +40,15 @@ export interface BundleSpec { readonly manifestPath?: string; /** Maximum allowed page load asset size for the bundles page load asset */ readonly pageLoadAssetSizeLimit?: number; + /** Information about how this bundle can be referenced by other bundles */ + readonly remoteInfo: { + /** the root package id that maps to this bundle */ + pkgId: string; + /** the valid sub-package imports, importing from the root of other bundles is not supported */ + targets: string[]; + }; + /** set this to `true` if the metrics for this bundle should be ignored */ + readonly ignoreMetrics: boolean; } export class Bundle { @@ -48,8 +56,6 @@ export class Bundle { public readonly type: BundleSpec['type']; /** Unique identifier for this bundle */ public readonly id: BundleSpec['id']; - /** directory names relative to the contextDir that can be imported from */ - public readonly publicDirNames: BundleSpec['publicDirNames']; /** * Absolute path to the root of the bundle context (plugin directory) * where the entry is resolved relative to and the default output paths @@ -64,25 +70,30 @@ export class Bundle { public readonly banner: BundleSpec['banner']; /** * Absolute path to a manifest file with "requiredBundles" which will be - * used to allow bundleRefs from this bundle to the exports of another bundle. - * Every bundle mentioned in the `requiredBundles` must be built together. + * used to allow references from this bundle to the exports of another bundle. */ public readonly manifestPath: BundleSpec['manifestPath']; /** Maximum allowed page load asset size for the bundles page load asset */ public readonly pageLoadAssetSizeLimit: BundleSpec['pageLoadAssetSizeLimit']; + /** Information about how this bundle can be references remotely */ + public readonly remoteInfo: BundleSpec['remoteInfo']; public readonly cache: BundleCache; + /** should this bundle's metrics be ignored? */ + public readonly ignoreMetrics: boolean; + constructor(spec: BundleSpec) { this.type = spec.type; this.id = spec.id; - this.publicDirNames = spec.publicDirNames; this.contextDir = spec.contextDir; this.sourceRoot = spec.sourceRoot; this.outputDir = spec.outputDir; this.manifestPath = spec.manifestPath; this.banner = spec.banner; this.pageLoadAssetSizeLimit = spec.pageLoadAssetSizeLimit; + this.remoteInfo = spec.remoteInfo; + this.ignoreMetrics = spec.ignoreMetrics; this.cache = new BundleCache(this.outputDir); } @@ -114,13 +125,14 @@ export class Bundle { return { type: this.type, id: this.id, - publicDirNames: this.publicDirNames, contextDir: this.contextDir, sourceRoot: this.sourceRoot, outputDir: this.outputDir, manifestPath: this.manifestPath, banner: this.banner, pageLoadAssetSizeLimit: this.pageLoadAssetSizeLimit, + remoteInfo: this.remoteInfo, + ignoreMetrics: this.ignoreMetrics, }; } @@ -143,25 +155,23 @@ export class Bundle { json = '{}'; } - let parsedManifest: { requiredPlugins?: string[]; requiredBundles?: string[] }; + let parsed; try { - parsedManifest = JSON.parse(json); + parsed = Jsonc.parse(json); } catch (error) { throw new Error( `unable to parse manifest at [${this.manifestPath}], error: [${error.message}]` ); } - if (typeof parsedManifest === 'object' && parsedManifest) { - const explicit = parsedManifest.requiredBundles || []; - const implicit = [...DEFAULT_IMPLICIT_BUNDLE_DEPS, ...(parsedManifest.requiredPlugins || [])]; - - if (isStringArray(explicit) && isStringArray(implicit)) { - return { - explicit, - implicit, - }; - } + if (isObj(parsed) && isObj(parsed.plugin)) { + return { + explicit: [...toStringArray(parsed.plugin.requiredBundles)], + implicit: [ + ...DEFAULT_IMPLICIT_BUNDLE_DEPS, + ...toStringArray(parsed.plugin.requiredPlugins), + ], + }; } throw new Error( @@ -201,11 +211,6 @@ export function parseBundles(json: string) { throw new Error('`bundles[]` must have a string `id` property'); } - const { publicDirNames } = spec; - if (!Array.isArray(publicDirNames) || !publicDirNames.every((d) => typeof d === 'string')) { - throw new Error('`bundles[]` must have an array of strings `publicDirNames` property'); - } - const { contextDir } = spec; if (!(typeof contextDir === 'string' && Path.isAbsolute(contextDir))) { throw new Error('`bundles[]` must have an absolute path `contextDir` property'); @@ -235,6 +240,11 @@ export function parseBundles(json: string) { } } + const { ignoreMetrics } = spec; + if (!(typeof ignoreMetrics === 'boolean')) { + throw new Error('`bundles[]` must have a boolean `ignoreMetrics` property'); + } + const { pageLoadAssetSizeLimit } = spec; if (pageLoadAssetSizeLimit !== undefined) { if (!(typeof pageLoadAssetSizeLimit === 'number')) { @@ -242,16 +252,38 @@ export function parseBundles(json: string) { } } + const { remoteInfo } = spec; + if (!(typeof remoteInfo === 'object' && remoteInfo !== null)) { + throw new Error('`bundles[]` must have a `remoteInfo` property which is an object'); + } + + const { pkgId, targets } = remoteInfo as UnknownVals; + if (typeof pkgId !== 'string') { + throw new Error('`bundles[].remoteInfo` must have a `pkgId` property which is a string'); + } + if ( + !Array.isArray(targets) || + targets.some((i) => typeof i !== 'string' || i.endsWith('/')) + ) { + throw new Error( + '`bundles[].remoteInfo` must have a `targets` property which is an array of strings that do not end with "/"' + ); + } + return new Bundle({ type, id, - publicDirNames, contextDir, sourceRoot, outputDir, banner, manifestPath, pageLoadAssetSizeLimit, + remoteInfo: { + pkgId, + targets, + }, + ignoreMetrics, }); }); } catch (error) { diff --git a/packages/kbn-optimizer/src/common/bundle_cache.ts b/packages/kbn-optimizer/src/common/bundle_cache.ts index 6a44504d875e22..8e246227eb7fef 100644 --- a/packages/kbn-optimizer/src/common/bundle_cache.ts +++ b/packages/kbn-optimizer/src/common/bundle_cache.ts @@ -18,7 +18,7 @@ export interface State { moduleCount?: number; workUnits?: number; referencedPaths?: string[]; - bundleRefExportIds?: string[]; + remoteBundleImportReqs?: string[]; dllRefKeys?: string[]; } @@ -87,8 +87,8 @@ export class BundleCache { return this.get().referencedPaths; } - public getBundleRefExportIds() { - return this.get().bundleRefExportIds; + public getRemoteBundleImportReqs() { + return this.get().remoteBundleImportReqs; } public getDllRefKeys() { diff --git a/packages/kbn-optimizer/src/common/bundle_refs.ts b/packages/kbn-optimizer/src/common/bundle_refs.ts deleted file mode 100644 index 49628fcf055887..00000000000000 --- a/packages/kbn-optimizer/src/common/bundle_refs.ts +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import Path from 'path'; - -import { Bundle } from './bundle'; -import { UnknownVals } from './ts_helpers'; - -export interface BundleRef { - bundleId: string; - contextDir: string; - contextPrefix: string; - entry: string; - exportId: string; -} - -export class BundleRefs { - static fromBundles(bundles: Bundle[]) { - return new BundleRefs( - bundles.reduce( - (acc: BundleRef[], b) => [ - ...acc, - ...b.publicDirNames.map( - (name): BundleRef => ({ - bundleId: b.id, - contextDir: b.contextDir, - // Path.resolve converts separators and strips the final separator - contextPrefix: Path.resolve(b.contextDir) + Path.sep, - entry: name, - exportId: `${b.type}/${b.id}/${name}`, - }) - ), - ], - [] - ) - ); - } - - static parseSpec(json: unknown) { - if (typeof json !== 'string') { - throw new Error('expected `bundleRefs` spec to be a JSON string'); - } - - let spec; - try { - spec = JSON.parse(json); - } catch (error) { - throw new Error('`bundleRefs` spec must be valid JSON'); - } - - if (!Array.isArray(spec)) { - throw new Error('`bundleRefs` spec must be an array'); - } - - return new BundleRefs( - spec.map((refSpec: UnknownVals): BundleRef => { - if (typeof refSpec !== 'object' || !refSpec) { - throw new Error('`bundleRefs[]` must be an object'); - } - - const { bundleId } = refSpec; - if (typeof bundleId !== 'string') { - throw new Error('`bundleRefs[].bundleId` must be a string'); - } - - const { contextDir } = refSpec; - if (typeof contextDir !== 'string' || !Path.isAbsolute(contextDir)) { - throw new Error('`bundleRefs[].contextDir` must be an absolute directory'); - } - - const { contextPrefix } = refSpec; - if (typeof contextPrefix !== 'string' || !Path.isAbsolute(contextPrefix)) { - throw new Error('`bundleRefs[].contextPrefix` must be an absolute directory'); - } - - const { entry } = refSpec; - if (typeof entry !== 'string') { - throw new Error('`bundleRefs[].entry` must be a string'); - } - - const { exportId } = refSpec; - if (typeof exportId !== 'string') { - throw new Error('`bundleRefs[].exportId` must be a string'); - } - - return { - bundleId, - contextDir, - contextPrefix, - entry, - exportId, - }; - }) - ); - } - - constructor(private readonly refs: BundleRef[]) {} - - public forBundleIds(bundleIds: string[]) { - return this.refs.filter((r) => bundleIds.includes(r.bundleId)); - } - - public filterByExportIds(exportIds: string[]) { - return this.refs.filter((r) => exportIds.includes(r.exportId)); - } - - public filterByContextPrefix(bundle: Bundle, absolutePath: string) { - return this.refs.filter( - (ref) => ref.bundleId !== bundle.id && absolutePath.startsWith(ref.contextPrefix) - ); - } - - public toSpecJson() { - return JSON.stringify(this.refs); - } -} diff --git a/packages/kbn-optimizer/src/common/bundle_remotes.ts b/packages/kbn-optimizer/src/common/bundle_remotes.ts new file mode 100644 index 00000000000000..e0a9b77ad4a62b --- /dev/null +++ b/packages/kbn-optimizer/src/common/bundle_remotes.ts @@ -0,0 +1,135 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { parseKbnImportReq } from '@kbn/repo-packages'; + +import { Bundle } from './bundle'; +import { isObj } from './ts_helpers'; + +export interface BundleRemote { + readonly bundleType: string; + readonly bundleId: string; + readonly pkgId: string; + readonly targets: readonly string[]; +} + +export class BundleRemotes { + static fromBundles(bundles: Bundle[]) { + return new BundleRemotes( + bundles.map((b) => ({ + bundleType: b.type, + bundleId: b.id, + ...b.remoteInfo, + })) + ); + } + + static parseSpec(json: unknown) { + if (typeof json !== 'string') { + throw new Error('expected `bundleRemotes` spec to be a JSON string'); + } + + let spec; + try { + spec = JSON.parse(json); + } catch (error) { + throw new Error('`bundleRemotes` spec must be valid JSON'); + } + + if (!Array.isArray(spec)) { + throw new Error('`bundleRemotes` spec must be an array'); + } + + return new BundleRemotes( + spec.map((remSpec) => { + if (!isObj(remSpec)) { + throw new Error('`bundleRemotes[]` must be an object'); + } + + const { bundleType, bundleId, pkgId, targets } = remSpec; + if (typeof bundleType !== 'string') { + throw new Error('`bundleRemotes[].bundleType` must be a string'); + } + + if (typeof bundleId !== 'string') { + throw new Error('`bundleRemotes[].bundleId` must be a string'); + } + + if (typeof pkgId !== 'string') { + throw new Error('`bundleRemotes[].pkgId` must be a string'); + } + + if (!Array.isArray(targets) || targets.some((t) => typeof t !== 'string')) { + throw new Error('`bundleRemotes[].targets` must be an array of strings'); + } + + return { + bundleType, + bundleId, + pkgId, + targets, + }; + }) + ); + } + + private byPkgId: Map; + constructor(private readonly remotes: BundleRemote[]) { + this.byPkgId = new Map(remotes.map((r) => [r.pkgId, r])); + + if (this.byPkgId.size !== remotes.length) { + const dups = remotes.filter((r) => { + if (this.byPkgId.has(r.pkgId)) { + this.byPkgId.delete(r.pkgId); + return false; + } + + return true; + }); + + throw new Error( + `invalid remotes, the following package ids belong to more than one remote: ${dups.join( + ', ' + )}` + ); + } + } + + public getForPkgId(pkgId: string) { + return this.byPkgId.get(pkgId); + } + + /** + * get the import requests were are passed in, and are also valid based on our config + */ + public unionImportReqs(importReqs: string[]) { + return importReqs.filter((r) => { + const parsed = parseKbnImportReq(r); + if (!parsed) { + return false; + } + + const own = this.byPkgId.get(parsed.pkgId); + if (!own) { + return false; + } + + return own.targets.includes(parsed.target); + }); + } + + public getValidImportReqs(bundleIds: string[]) { + const filter = new Set(bundleIds); + const remotes = this.remotes.filter((r) => filter.has(r.bundleId)); + return remotes.flatMap((r) => r.targets.map((t) => (t === '' ? r.pkgId : `${r.pkgId}/${t}`))); + } + + public toSpecJson() { + return JSON.stringify(this.remotes); + } +} diff --git a/packages/kbn-optimizer/src/common/index.ts b/packages/kbn-optimizer/src/common/index.ts index f516e5f84198f3..324be7dc76a553 100644 --- a/packages/kbn-optimizer/src/common/index.ts +++ b/packages/kbn-optimizer/src/common/index.ts @@ -8,7 +8,7 @@ export * from './bundle'; export * from './bundle_cache'; -export * from './bundle_refs'; +export * from './bundle_remotes'; export * from './worker_config'; export * from './worker_messages'; export * from './compiler_messages'; diff --git a/packages/kbn-optimizer/src/common/ts_helpers.ts b/packages/kbn-optimizer/src/common/ts_helpers.ts index bd10b97d55f027..7883687819f35e 100644 --- a/packages/kbn-optimizer/src/common/ts_helpers.ts +++ b/packages/kbn-optimizer/src/common/ts_helpers.ts @@ -13,3 +13,7 @@ export type UnknownVals = { [k in keyof T]: unknown; }; + +export function isObj(val: unknown): val is Record { + return typeof val === 'object' && val !== null; +} diff --git a/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap b/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap deleted file mode 100644 index f93a80f82d06e7..00000000000000 --- a/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap +++ /dev/null @@ -1,793 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`builds expected bundles, saves bundle counts to metadata: OptimizerConfig 1`] = ` -OptimizerConfig { - "bundles": Array [ - Bundle { - "banner": undefined, - "cache": BundleCache { - "path": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/target/public/.kbn-optimizer-cache, - "state": undefined, - }, - "contextDir": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar, - "id": "bar", - "manifestPath": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/kibana.json, - "outputDir": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/target/public, - "pageLoadAssetSizeLimit": undefined, - "publicDirNames": Array [ - "public", - ], - "sourceRoot": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo, - "type": "plugin", - }, - Bundle { - "banner": undefined, - "cache": BundleCache { - "path": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/target/public/.kbn-optimizer-cache, - "state": undefined, - }, - "contextDir": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo, - "id": "foo", - "manifestPath": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/kibana.json, - "outputDir": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/target/public, - "pageLoadAssetSizeLimit": undefined, - "publicDirNames": Array [ - "public", - ], - "sourceRoot": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo, - "type": "plugin", - }, - Bundle { - "banner": "/*! Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one or more contributor license agreements. - * Licensed under the Elastic License 2.0; you may not use this file except in compliance with the Elastic License 2.0. */ -", - "cache": BundleCache { - "path": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/x-pack/baz/target/public/.kbn-optimizer-cache, - "state": undefined, - }, - "contextDir": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/x-pack/baz, - "id": "baz", - "manifestPath": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/x-pack/baz/kibana.json, - "outputDir": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/x-pack/baz/target/public, - "pageLoadAssetSizeLimit": undefined, - "publicDirNames": Array [ - "public", - ], - "sourceRoot": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo, - "type": "plugin", - }, - ], - "cache": true, - "dist": false, - "filteredBundles": Array [ - Bundle { - "banner": undefined, - "cache": BundleCache { - "path": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/target/public/.kbn-optimizer-cache, - "state": undefined, - }, - "contextDir": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar, - "id": "bar", - "manifestPath": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/kibana.json, - "outputDir": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/target/public, - "pageLoadAssetSizeLimit": undefined, - "publicDirNames": Array [ - "public", - ], - "sourceRoot": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo, - "type": "plugin", - }, - Bundle { - "banner": undefined, - "cache": BundleCache { - "path": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/target/public/.kbn-optimizer-cache, - "state": undefined, - }, - "contextDir": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo, - "id": "foo", - "manifestPath": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/kibana.json, - "outputDir": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/target/public, - "pageLoadAssetSizeLimit": undefined, - "publicDirNames": Array [ - "public", - ], - "sourceRoot": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo, - "type": "plugin", - }, - Bundle { - "banner": "/*! Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one or more contributor license agreements. - * Licensed under the Elastic License 2.0; you may not use this file except in compliance with the Elastic License 2.0. */ -", - "cache": BundleCache { - "path": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/x-pack/baz/target/public/.kbn-optimizer-cache, - "state": undefined, - }, - "contextDir": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/x-pack/baz, - "id": "baz", - "manifestPath": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/x-pack/baz/kibana.json, - "outputDir": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/x-pack/baz/target/public, - "pageLoadAssetSizeLimit": undefined, - "publicDirNames": Array [ - "public", - ], - "sourceRoot": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo, - "type": "plugin", - }, - ], - "inspectWorkers": false, - "maxWorkerCount": 1, - "plugins": Array [ - Object { - "directory": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar, - "extraPublicDirs": Array [], - "id": "bar", - "isUiPlugin": true, - "manifestPath": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/kibana.json, - }, - Object { - "directory": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo, - "extraPublicDirs": Array [], - "id": "foo", - "isUiPlugin": true, - "manifestPath": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/kibana.json, - }, - Object { - "directory": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/nested/baz, - "extraPublicDirs": Array [], - "id": "baz", - "isUiPlugin": false, - "manifestPath": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/nested/baz/kibana.json, - }, - Object { - "directory": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/x-pack/baz, - "extraPublicDirs": Array [], - "id": "baz", - "isUiPlugin": true, - "manifestPath": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/x-pack/baz/kibana.json, - }, - ], - "profileWebpack": false, - "repoRoot": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo, - "themeTags": Array [ - "v8dark", - "v8light", - ], - "watch": false, -} -`; - -exports[`prepares assets for distribution: bar bundle 1`] = ` -"!(function (e) { - var n = {}; - function t(r) { - if (n[r]) return n[r].exports; - var o = (n[r] = { i: r, l: !1, exports: {} }); - return e[r].call(o.exports, o, o.exports, t), (o.l = !0), o.exports; - } - (t.m = e), - (t.c = n), - (t.d = function (e, n, r) { - t.o(e, n) || Object.defineProperty(e, n, { enumerable: !0, get: r }); - }), - (t.r = function (e) { - \\"undefined\\" != typeof Symbol && - Symbol.toStringTag && - Object.defineProperty(e, Symbol.toStringTag, { value: \\"Module\\" }), - Object.defineProperty(e, \\"__esModule\\", { value: !0 }); - }), - (t.t = function (e, n) { - if ((1 & n && (e = t(e)), 8 & n)) return e; - if (4 & n && \\"object\\" == typeof e && e && e.__esModule) return e; - var r = Object.create(null); - if ( - (t.r(r), - Object.defineProperty(r, \\"default\\", { enumerable: !0, value: e }), - 2 & n && \\"string\\" != typeof e) - ) - for (var o in e) - t.d( - r, - o, - function (n) { - return e[n]; - }.bind(null, o) - ); - return r; - }), - (t.n = function (e) { - var n = - e && e.__esModule - ? function () { - return e.default; - } - : function () { - return e; - }; - return t.d(n, \\"a\\", n), n; - }), - (t.o = function (e, n) { - return Object.prototype.hasOwnProperty.call(e, n); - }), - (t.p = \\"\\"), - t((t.s = 3)); -})([ - function (e, n, t) { - \\"use strict\\"; - var r, - o = (function () { - var e = {}; - return function (n) { - if (void 0 === e[n]) { - var t = document.querySelector(n); - if ( - window.HTMLIFrameElement && - t instanceof window.HTMLIFrameElement - ) - try { - t = t.contentDocument.head; - } catch (e) { - t = null; - } - e[n] = t; - } - return e[n]; - }; - })(), - i = []; - function a(e) { - for (var n = -1, t = 0; t < i.length; t++) - if (i[t].identifier === e) { - n = t; - break; - } - return n; - } - function c(e, n) { - for (var t = {}, r = [], o = 0; o < e.length; o++) { - var c = e[o], - u = n.base ? c[0] + n.base : c[0], - s = t[u] || 0, - f = \\"\\".concat(u, \\" \\").concat(s); - t[u] = s + 1; - var l = a(f), - d = { css: c[1], media: c[2], sourceMap: c[3] }; - -1 !== l - ? (i[l].references++, i[l].updater(d)) - : i.push({ identifier: f, updater: v(d, n), references: 1 }), - r.push(f); - } - return r; - } - function u(e) { - var n = document.createElement(\\"style\\"), - r = e.attributes || {}; - if (void 0 === r.nonce) { - var i = t.nc; - i && (r.nonce = i); - } - if ( - (Object.keys(r).forEach(function (e) { - n.setAttribute(e, r[e]); - }), - \\"function\\" == typeof e.insert) - ) - e.insert(n); - else { - var a = o(e.insert || \\"head\\"); - if (!a) - throw new Error( - \\"Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.\\" - ); - a.appendChild(n); - } - return n; - } - var s, - f = - ((s = []), - function (e, n) { - return (s[e] = n), s.filter(Boolean).join(\\"\\\\n\\"); - }); - function l(e, n, t, r) { - var o = t - ? \\"\\" - : r.media - ? \\"@media \\".concat(r.media, \\" {\\").concat(r.css, \\"}\\") - : r.css; - if (e.styleSheet) e.styleSheet.cssText = f(n, o); - else { - var i = document.createTextNode(o), - a = e.childNodes; - a[n] && e.removeChild(a[n]), - a.length ? e.insertBefore(i, a[n]) : e.appendChild(i); - } - } - function d(e, n, t) { - var r = t.css, - o = t.media, - i = t.sourceMap; - if ( - (o ? e.setAttribute(\\"media\\", o) : e.removeAttribute(\\"media\\"), - i && - \\"undefined\\" != typeof btoa && - (r += \\"\\\\n/*# sourceMappingURL=data:application/json;base64,\\".concat( - btoa(unescape(encodeURIComponent(JSON.stringify(i)))), - \\" */\\" - )), - e.styleSheet) - ) - e.styleSheet.cssText = r; - else { - for (; e.firstChild; ) e.removeChild(e.firstChild); - e.appendChild(document.createTextNode(r)); - } - } - var p = null, - b = 0; - function v(e, n) { - var t, r, o; - if (n.singleton) { - var i = b++; - (t = p || (p = u(n))), - (r = l.bind(null, t, i, !1)), - (o = l.bind(null, t, i, !0)); - } else - (t = u(n)), - (r = d.bind(null, t, n)), - (o = function () { - !(function (e) { - if (null === e.parentNode) return !1; - e.parentNode.removeChild(e); - })(t); - }); - return ( - r(e), - function (n) { - if (n) { - if ( - n.css === e.css && - n.media === e.media && - n.sourceMap === e.sourceMap - ) - return; - r((e = n)); - } else o(); - } - ); - } - e.exports = function (e, n) { - (n = n || {}).singleton || - \\"boolean\\" == typeof n.singleton || - (n.singleton = - (void 0 === r && - (r = Boolean(window && document && document.all && !window.atob)), - r)); - var t = c((e = e || []), n); - return function (e) { - if ( - ((e = e || []), - \\"[object Array]\\" === Object.prototype.toString.call(e)) - ) { - for (var r = 0; r < t.length; r++) { - var o = a(t[r]); - i[o].references--; - } - for (var u = c(e, n), s = 0; s < t.length; s++) { - var f = a(t[s]); - 0 === i[f].references && (i[f].updater(), i.splice(f, 1)); - } - t = u; - } - }; - }; - }, - function (e, n, t) { - \\"use strict\\"; - e.exports = function (e) { - var n = []; - return ( - (n.toString = function () { - return this.map(function (n) { - var t = (function (e, n) { - var t, - r, - o, - i = e[1] || \\"\\", - a = e[3]; - if (!a) return i; - if (n && \\"function\\" == typeof btoa) { - var c = - ((t = a), - (r = btoa(unescape(encodeURIComponent(JSON.stringify(t))))), - (o = - \\"sourceMappingURL=data:application/json;charset=utf-8;base64,\\".concat( - r - )), - \\"/*# \\".concat(o, \\" */\\")), - u = a.sources.map(function (e) { - return \\"/*# sourceURL=\\" - .concat(a.sourceRoot || \\"\\") - .concat(e, \\" */\\"); - }); - return [i].concat(u).concat([c]).join(\\"\\\\n\\"); - } - return [i].join(\\"\\\\n\\"); - })(n, e); - return n[2] ? \\"@media \\".concat(n[2], \\" {\\").concat(t, \\"}\\") : t; - }).join(\\"\\"); - }), - (n.i = function (e, t, r) { - \\"string\\" == typeof e && (e = [[null, e, \\"\\"]]); - var o = {}; - if (r) - for (var i = 0; i < this.length; i++) { - var a = this[i][0]; - null != a && (o[a] = !0); - } - for (var c = 0; c < e.length; c++) { - var u = [].concat(e[c]); - (r && o[u[0]]) || - (t && - (u[2] - ? (u[2] = \\"\\".concat(t, \\" and \\").concat(u[2])) - : (u[2] = t)), - n.push(u)); - } - }), - n - ); - }; - }, - function (e, n, t) { - t.r(n); - var r = __kbnBundles__.get(\\"plugin/foo/public\\"); - Object.defineProperties(n, Object.getOwnPropertyDescriptors(r)); - }, - function (e, n, t) { - t(4), __kbnBundles__.define(\\"plugin/bar/public\\", t, 15); - }, - function (e, n, t) { - t.p = window.__kbnPublicPath__.bar; - }, - function (e, n, t) { - switch (window.__kbnThemeTag__) { - case \\"v8dark\\": - return t(6); - case \\"v8light\\": - return t(8); - } - }, - function (e, n, t) { - var r = t(0), - o = t(7); - \\"string\\" == typeof (o = o.__esModule ? o.default : o) && - (o = [[e.i, o, \\"\\"]]); - r(o, { insert: \\"head\\", singleton: !1 }), (e.exports = o.locals || {}); - }, - function (e, n, t) { - (n = t(1)(!1)).push([e.i, \\"p{background-color:#639}body{width:12}\\", \\"\\"]), - (e.exports = n); - }, - function (e, n, t) { - var r = t(0), - o = t(9); - \\"string\\" == typeof (o = o.__esModule ? o.default : o) && - (o = [[e.i, o, \\"\\"]]); - r(o, { insert: \\"head\\", singleton: !1 }), (e.exports = o.locals || {}); - }, - function (e, n, t) { - (n = t(1)(!1)).push([e.i, \\"p{background-color:#639}body{width:13}\\", \\"\\"]), - (e.exports = n); - }, - function (e, n, t) { - switch (window.__kbnThemeTag__) { - case \\"v8dark\\": - return t(11); - case \\"v8light\\": - return t(13); - } - }, - function (e, n, t) { - var r = t(0), - o = t(12); - \\"string\\" == typeof (o = o.__esModule ? o.default : o) && - (o = [[e.i, o, \\"\\"]]); - r(o, { insert: \\"head\\", singleton: !1 }), (e.exports = o.locals || {}); - }, - function (e, n, t) { - (n = t(1)(!1)).push([e.i, \\"body{color:green}\\", \\"\\"]), (e.exports = n); - }, - function (e, n, t) { - var r = t(0), - o = t(14); - \\"string\\" == typeof (o = o.__esModule ? o.default : o) && - (o = [[e.i, o, \\"\\"]]); - r(o, { insert: \\"head\\", singleton: !1 }), (e.exports = o.locals || {}); - }, - function (e, n, t) { - (n = t(1)(!1)).push([e.i, \\"body{color:green}\\", \\"\\"]), (e.exports = n); - }, - function (e, n, t) { - \\"use strict\\"; - t.r(n), - t.d(n, \\"barLibFn\\", function () { - return o; - }), - t.d(n, \\"fooLibFn\\", function () { - return r.fooLibFn; - }), - t(5), - t(10); - var r = t(2); - function o() { - return \\"bar\\"; - } - }, -]); -" -`; - -exports[`prepares assets for distribution: baz bundle 1`] = ` -"/*! Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one or more contributor license agreements. - * Licensed under the Elastic License 2.0; you may not use this file except in compliance with the Elastic License 2.0. */ !(function ( - e -) { - var n = {}; - function t(r) { - if (n[r]) return n[r].exports; - var o = (n[r] = { i: r, l: !1, exports: {} }); - return e[r].call(o.exports, o, o.exports, t), (o.l = !0), o.exports; - } - (t.m = e), - (t.c = n), - (t.d = function (e, n, r) { - t.o(e, n) || Object.defineProperty(e, n, { enumerable: !0, get: r }); - }), - (t.r = function (e) { - \\"undefined\\" != typeof Symbol && - Symbol.toStringTag && - Object.defineProperty(e, Symbol.toStringTag, { value: \\"Module\\" }), - Object.defineProperty(e, \\"__esModule\\", { value: !0 }); - }), - (t.t = function (e, n) { - if ((1 & n && (e = t(e)), 8 & n)) return e; - if (4 & n && \\"object\\" == typeof e && e && e.__esModule) return e; - var r = Object.create(null); - if ( - (t.r(r), - Object.defineProperty(r, \\"default\\", { enumerable: !0, value: e }), - 2 & n && \\"string\\" != typeof e) - ) - for (var o in e) - t.d( - r, - o, - function (n) { - return e[n]; - }.bind(null, o) - ); - return r; - }), - (t.n = function (e) { - var n = - e && e.__esModule - ? function () { - return e.default; - } - : function () { - return e; - }; - return t.d(n, \\"a\\", n), n; - }), - (t.o = function (e, n) { - return Object.prototype.hasOwnProperty.call(e, n); - }), - (t.p = \\"\\"), - t((t.s = 0)); -})([ - function (e, n, t) { - t(1), __kbnBundles__.define(\\"plugin/baz/public\\", t, 2); - }, - function (e, n, t) { - t.p = window.__kbnPublicPath__.baz; - }, - function (e, n) { - console.log(\\"plugin in an x-pack dir\\"); - }, -]); -" -`; - -exports[`prepares assets for distribution: foo async bundle 1`] = ` -"(window.foo_bundle_jsonpfunction = window.foo_bundle_jsonpfunction || []).push([ - [1], - { - 3: function (n, o, u) { - \\"use strict\\"; - function f() {} - u.r(o), - u.d(o, \\"foo\\", function () { - return f; - }); - }, - }, -]); -" -`; - -exports[`prepares assets for distribution: foo bundle 1`] = ` -"!(function (n) { - function e(e) { - for (var t, o, u = e[0], i = e[1], c = 0, a = []; c < u.length; c++) - (o = u[c]), - Object.prototype.hasOwnProperty.call(r, o) && r[o] && a.push(r[o][0]), - (r[o] = 0); - for (t in i) Object.prototype.hasOwnProperty.call(i, t) && (n[t] = i[t]); - for (f && f(e); a.length; ) a.shift()(); - } - var t = {}, - r = { 0: 0 }; - function o(e) { - if (t[e]) return t[e].exports; - var r = (t[e] = { i: e, l: !1, exports: {} }); - return n[e].call(r.exports, r, r.exports, o), (r.l = !0), r.exports; - } - (o.e = function (n) { - var e = [], - t = r[n]; - if (0 !== t) - if (t) e.push(t[2]); - else { - var u = new Promise(function (e, o) { - t = r[n] = [e, o]; - }); - e.push((t[2] = u)); - var i, - c = document.createElement(\\"script\\"); - (c.charset = \\"utf-8\\"), - (c.timeout = 120), - o.nc && c.setAttribute(\\"nonce\\", o.nc), - (c.src = (function (n) { - return o.p + \\"foo.chunk.\\" + n + \\".js\\"; - })(n)); - var f = new Error(); - i = function (e) { - (c.onerror = c.onload = null), clearTimeout(a); - var t = r[n]; - if (0 !== t) { - if (t) { - var o = e && (\\"load\\" === e.type ? \\"missing\\" : e.type), - u = e && e.target && e.target.src; - (f.message = - \\"Loading chunk \\" + n + \\" failed.\\\\n(\\" + o + \\": \\" + u + \\")\\"), - (f.name = \\"ChunkLoadError\\"), - (f.type = o), - (f.request = u), - t[1](f); - } - r[n] = void 0; - } - }; - var a = setTimeout(function () { - i({ type: \\"timeout\\", target: c }); - }, 12e4); - (c.onerror = c.onload = i), document.head.appendChild(c); - } - return Promise.all(e); - }), - (o.m = n), - (o.c = t), - (o.d = function (n, e, t) { - o.o(n, e) || Object.defineProperty(n, e, { enumerable: !0, get: t }); - }), - (o.r = function (n) { - \\"undefined\\" != typeof Symbol && - Symbol.toStringTag && - Object.defineProperty(n, Symbol.toStringTag, { value: \\"Module\\" }), - Object.defineProperty(n, \\"__esModule\\", { value: !0 }); - }), - (o.t = function (n, e) { - if ((1 & e && (n = o(n)), 8 & e)) return n; - if (4 & e && \\"object\\" == typeof n && n && n.__esModule) return n; - var t = Object.create(null); - if ( - (o.r(t), - Object.defineProperty(t, \\"default\\", { enumerable: !0, value: n }), - 2 & e && \\"string\\" != typeof n) - ) - for (var r in n) - o.d( - t, - r, - function (e) { - return n[e]; - }.bind(null, r) - ); - return t; - }), - (o.n = function (n) { - var e = - n && n.__esModule - ? function () { - return n.default; - } - : function () { - return n; - }; - return o.d(e, \\"a\\", e), e; - }), - (o.o = function (n, e) { - return Object.prototype.hasOwnProperty.call(n, e); - }), - (o.p = \\"\\"), - (o.oe = function (n) { - throw (console.error(n), n); - }); - var u = (window.foo_bundle_jsonpfunction = - window.foo_bundle_jsonpfunction || []), - i = u.push.bind(u); - (u.push = e), (u = u.slice()); - for (var c = 0; c < u.length; c++) e(u[c]); - var f = i; - o((o.s = 0)); -})([ - function (n, e, t) { - t(1), __kbnBundles__.define(\\"plugin/foo/public\\", t, 2); - }, - function (n, e, t) { - t.p = window.__kbnPublicPath__.foo; - }, - function (n, e, t) { - \\"use strict\\"; - function r() { - return \\"foo\\"; - } - t.r(e), - t.d(e, \\"fooLibFn\\", function () { - return r; - }), - t.d(e, \\"ext\\", function () { - return o; - }), - t.d(e, \\"getFoo\\", function () { - return u; - }); - const o = \\"TRUE\\"; - async function u() { - return await t.e(1).then(t.bind(null, 3)); - } - }, -]); -" -`; - -exports[`prepares assets for distribution: metrics.json 1`] = ` -"[ - { - \\"group\\": \\"@kbn/optimizer bundle module count\\", - \\"id\\": \\"foo\\", - \\"value\\": 6 - }, - { - \\"group\\": \\"page load bundle size\\", - \\"id\\": \\"foo\\", - \\"value\\": 2457, - \\"limitConfigPath\\": \\"packages/kbn-optimizer/limits.yml\\" - }, - { - \\"group\\": \\"async chunks size\\", - \\"id\\": \\"foo\\", - \\"value\\": 173 - }, - { - \\"group\\": \\"async chunk count\\", - \\"id\\": \\"foo\\", - \\"value\\": 1 - }, - { - \\"group\\": \\"miscellaneous assets size\\", - \\"id\\": \\"foo\\", - \\"value\\": 0 - } -]" -`; diff --git a/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts b/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts deleted file mode 100644 index 1e84f4443b3754..00000000000000 --- a/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts +++ /dev/null @@ -1,264 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import Path from 'path'; -import Fs from 'fs'; -import Zlib from 'zlib'; -import { inspect } from 'util'; -import prettier from 'prettier'; - -import cpy from 'cpy'; -import del from 'del'; -import { tap, filter } from 'rxjs/operators'; -import { REPO_ROOT } from '@kbn/repo-info'; -import { ToolingLog } from '@kbn/tooling-log'; -import { createReplaceSerializer } from '@kbn/jest-serializers'; -import { runOptimizer, OptimizerConfig, OptimizerUpdate, logOptimizerState } from '../..'; - -import { allValuesFrom } from '../common'; - -const TMP_DIR = Path.resolve(__dirname, '../__fixtures__/__tmp__'); -const MOCK_REPO_SRC = Path.resolve(__dirname, '../__fixtures__/mock_repo'); -const MOCK_REPO_DIR = Path.resolve(TMP_DIR, 'mock_repo'); - -expect.addSnapshotSerializer({ - serialize: (value: string) => value.split(REPO_ROOT).join('').replace(/\\/g, '/'), - test: (value: any) => typeof value === 'string' && value.includes(REPO_ROOT), -}); - -expect.addSnapshotSerializer(createReplaceSerializer(/\w+-fastbuild/, '-fastbuild')); - -const log = new ToolingLog({ - level: 'error', - writeTo: { - write(chunk) { - if (chunk.endsWith('\n')) { - chunk = chunk.slice(0, -1); - } - // eslint-disable-next-line no-console - console.error(chunk); - }, - }, -}); - -beforeAll(async () => { - await del(TMP_DIR); - await cpy('**/*', MOCK_REPO_DIR, { - cwd: MOCK_REPO_SRC, - parents: true, - deep: true, - }); -}); - -afterAll(async () => { - await del(TMP_DIR); -}); - -it('builds expected bundles, saves bundle counts to metadata', async () => { - const config = OptimizerConfig.create({ - repoRoot: MOCK_REPO_DIR, - pluginScanDirs: [Path.resolve(MOCK_REPO_DIR, 'plugins'), Path.resolve(MOCK_REPO_DIR, 'x-pack')], - maxWorkerCount: 1, - dist: false, - }); - - expect(config).toMatchSnapshot('OptimizerConfig'); - - const msgs = await allValuesFrom( - runOptimizer(config).pipe( - logOptimizerState(log, config), - filter((x) => x.event?.type !== 'worker stdio') - ) - ); - - const assert = (statement: string, truth: boolean, altStates?: OptimizerUpdate[]) => { - if (!truth) { - throw new Error( - `expected optimizer to ${statement}, states: ${inspect(altStates || msgs, { - colors: true, - depth: Infinity, - })}` - ); - } - }; - - const initializingStates = msgs.filter((msg) => msg.state.phase === 'initializing'); - assert('produce at least one initializing event', initializingStates.length >= 1); - - const bundleCacheStates = msgs.filter( - (msg) => - (msg.event?.type === 'bundle cached' || msg.event?.type === 'bundle not cached') && - msg.state.phase === 'initializing' - ); - assert('produce three bundle cache events while initializing', bundleCacheStates.length === 3); - - const initializedStates = msgs.filter((msg) => msg.state.phase === 'initialized'); - assert('produce at least one initialized event', initializedStates.length >= 1); - - const workerStarted = msgs.filter((msg) => msg.event?.type === 'worker started'); - assert('produce one worker started event', workerStarted.length === 1); - - const runningStates = msgs.filter((msg) => msg.state.phase === 'running'); - assert( - 'produce three to five "running" states', - runningStates.length >= 3 && runningStates.length <= 5 - ); - - const bundleNotCachedEvents = msgs.filter((msg) => msg.event?.type === 'bundle not cached'); - assert('produce three "bundle not cached" events', bundleNotCachedEvents.length === 3); - - const successStates = msgs.filter((msg) => msg.state.phase === 'success'); - assert( - 'produce one to three "compiler success" states', - successStates.length >= 1 && successStates.length <= 3 - ); - - const otherStates = msgs.filter( - (msg) => - msg.state.phase !== 'initializing' && - msg.state.phase !== 'success' && - msg.state.phase !== 'running' && - msg.state.phase !== 'initialized' && - msg.event?.type !== 'bundle not cached' - ); - assert('produce zero unexpected states', otherStates.length === 0, otherStates); - - const foo = config.bundles.find((b) => b.id === 'foo')!; - expect(foo).toBeTruthy(); - foo.cache.refresh(); - expect(foo.cache.getModuleCount()).toBe(6); - expect(foo.cache.getReferencedPaths()).toMatchInlineSnapshot(` - Array [ - /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/kibana.json, - /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/async_import.ts, - /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/ext.ts, - /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/index.ts, - /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/lib.ts, - /packages/kbn-optimizer/src/worker/entry_point_creator.ts, - /packages/kbn-ui-shared-deps-npm/src/public_path_module_creator.js, - ] - `); - - const bar = config.bundles.find((b) => b.id === 'bar')!; - expect(bar).toBeTruthy(); - bar.cache.refresh(); - expect(bar.cache.getModuleCount()).toBe( - // code + styles + style/css-loader runtimes + public path updater - 16 - ); - - expect(bar.cache.getReferencedPaths()).toMatchInlineSnapshot(` - Array [ - /node_modules/css-loader/package.json, - /node_modules/style-loader/package.json, - /packages/kbn-optimizer/postcss.config.js, - /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/kibana.json, - /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/public/index.scss, - /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/public/index.ts, - /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/public/legacy/_other_styles.scss, - /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/public/legacy/styles.scss, - /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/public/lib.ts, - /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/src/core/public/styles/core_app/_globals_v8dark.scss, - /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/src/core/public/styles/core_app/_globals_v8light.scss, - /packages/kbn-optimizer/src/worker/entry_point_creator.ts, - /packages/kbn-ui-shared-deps-npm/src/public_path_module_creator.js, - ] - `); - - const baz = config.bundles.find((b) => b.id === 'baz')!; - expect(baz).toBeTruthy(); - baz.cache.refresh(); - expect(baz.cache.getModuleCount()).toBe(3); - - expect(baz.cache.getReferencedPaths()).toMatchInlineSnapshot(` - Array [ - /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/x-pack/baz/kibana.json, - /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/x-pack/baz/public/index.ts, - /packages/kbn-optimizer/src/worker/entry_point_creator.ts, - /packages/kbn-ui-shared-deps-npm/src/public_path_module_creator.js, - ] - `); -}); - -it('uses cache on second run and exist cleanly', async () => { - const config = OptimizerConfig.create({ - repoRoot: MOCK_REPO_DIR, - pluginScanDirs: [Path.resolve(MOCK_REPO_DIR, 'plugins'), Path.resolve(MOCK_REPO_DIR, 'x-pack')], - maxWorkerCount: 1, - dist: false, - }); - - const msgs = await allValuesFrom( - runOptimizer(config).pipe( - tap((state) => { - if (state.event?.type === 'worker stdio') { - // eslint-disable-next-line no-console - console.log('worker', state.event.stream, state.event.line); - } - }) - ) - ); - - expect(msgs.map((m) => m.state.phase)).toMatchInlineSnapshot(` - Array [ - "initializing", - "initializing", - "initializing", - "initializing", - "initialized", - "success", - ] - `); -}); - -it('prepares assets for distribution', async () => { - if (process.env.CODE_COVERAGE) { - // test fails when testing coverage because source includes instrumentation, so skip it - return; - } - const config = OptimizerConfig.create({ - repoRoot: MOCK_REPO_DIR, - pluginScanDirs: [Path.resolve(MOCK_REPO_DIR, 'plugins'), Path.resolve(MOCK_REPO_DIR, 'x-pack')], - maxWorkerCount: 1, - dist: true, - }); - - await allValuesFrom(runOptimizer(config).pipe(logOptimizerState(log, config))); - - expect( - Fs.readFileSync(Path.resolve(MOCK_REPO_DIR, 'plugins/foo/target/public/metrics.json'), 'utf8') - ).toMatchSnapshot('metrics.json'); - - expectFileMatchesSnapshotWithCompression('plugins/foo/target/public/foo.plugin.js', 'foo bundle'); - expectFileMatchesSnapshotWithCompression( - 'plugins/foo/target/public/foo.chunk.1.js', - 'foo async bundle' - ); - expectFileMatchesSnapshotWithCompression('plugins/bar/target/public/bar.plugin.js', 'bar bundle'); - expectFileMatchesSnapshotWithCompression('x-pack/baz/target/public/baz.plugin.js', 'baz bundle'); -}); - -/** - * Verifies that the file matches the expected output and has matching compressed variants. - */ -const expectFileMatchesSnapshotWithCompression = (filePath: string, snapshotLabel: string) => { - const path = Path.resolve(MOCK_REPO_DIR, filePath); - const raw = Fs.readFileSync(path, 'utf8'); - const pretty = prettier.format(raw, { - filepath: path, - }); - - expect(pretty).toMatchSnapshot(snapshotLabel); - - // Verify the brotli variant matches - expect( - Zlib.brotliDecompressSync( - Fs.readFileSync(Path.resolve(MOCK_REPO_DIR, `${filePath}.br`)) - ).toString() - ).toEqual(raw); -}; diff --git a/packages/kbn-optimizer/src/integration_tests/bundle_cache.test.ts b/packages/kbn-optimizer/src/integration_tests/bundle_cache.test.ts deleted file mode 100644 index bfec5800abebef..00000000000000 --- a/packages/kbn-optimizer/src/integration_tests/bundle_cache.test.ts +++ /dev/null @@ -1,384 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import Path from 'path'; - -import cpy from 'cpy'; -import del from 'del'; -import { createAbsolutePathSerializer, createStripAnsiSerializer } from '@kbn/jest-serializers'; - -import { OptimizerConfig } from '../optimizer/optimizer_config'; -import { allValuesFrom, Bundle, Hashes, ParsedDllManifest } from '../common'; -import { getBundleCacheEvent$ } from '../optimizer/bundle_cache'; - -const TMP_DIR = Path.resolve(__dirname, '../__fixtures__/__tmp__'); -const MOCK_REPO_SRC = Path.resolve(__dirname, '../__fixtures__/mock_repo'); -const MOCK_REPO_DIR = Path.resolve(TMP_DIR, 'mock_repo'); - -jest.mock('../common/dll_manifest', () => ({ - parseDllManifest: jest.fn(), -})); - -const EMPTY_DLL_MANIFEST: ParsedDllManifest = { - name: 'foo', - content: {}, -}; -jest.requireMock('../common/dll_manifest').parseDllManifest.mockReturnValue(EMPTY_DLL_MANIFEST); - -expect.addSnapshotSerializer({ - print: () => '', - test: (v) => v instanceof Bundle, -}); -expect.addSnapshotSerializer(createStripAnsiSerializer()); -expect.addSnapshotSerializer(createAbsolutePathSerializer(MOCK_REPO_DIR)); - -beforeEach(async () => { - await del(TMP_DIR); - await cpy('**/*', MOCK_REPO_DIR, { - cwd: MOCK_REPO_SRC, - parents: true, - deep: true, - }); -}); - -afterEach(async () => { - await del(TMP_DIR); -}); - -it('emits "bundle cached" event when everything is updated', async () => { - const config = OptimizerConfig.create({ - repoRoot: MOCK_REPO_DIR, - pluginScanDirs: [], - pluginPaths: [Path.resolve(MOCK_REPO_DIR, 'plugins/foo')], - maxWorkerCount: 1, - }); - const [bundle] = config.bundles; - - const optimizerCacheKey = 'optimizerCacheKey'; - const referencedPaths = [ - Path.resolve(MOCK_REPO_DIR, 'plugins/foo/public/ext.ts'), - Path.resolve(MOCK_REPO_DIR, 'plugins/foo/public/index.ts'), - Path.resolve(MOCK_REPO_DIR, 'plugins/foo/public/lib.ts'), - ]; - const hashes = await Hashes.ofFiles(referencedPaths); - const cacheKey = bundle.createCacheKey(referencedPaths, hashes, EMPTY_DLL_MANIFEST, []); - - bundle.cache.set({ - cacheKey, - optimizerCacheKey, - referencedPaths, - moduleCount: referencedPaths.length, - bundleRefExportIds: [], - dllRefKeys: [], - }); - - const cacheEvents = await allValuesFrom(getBundleCacheEvent$(config, optimizerCacheKey)); - - expect(cacheEvents).toMatchInlineSnapshot(` - Array [ - Object { - "bundle": , - "type": "bundle cached", - }, - ] - `); -}); - -it('emits "bundle not cached" event when cacheKey is up to date but caching is disabled in config', async () => { - const config = OptimizerConfig.create({ - repoRoot: MOCK_REPO_DIR, - pluginScanDirs: [], - pluginPaths: [Path.resolve(MOCK_REPO_DIR, 'plugins/foo')], - maxWorkerCount: 1, - cache: false, - }); - const [bundle] = config.bundles; - - const optimizerCacheKey = 'optimizerCacheKey'; - const referencedPaths = [ - Path.resolve(MOCK_REPO_DIR, 'plugins/foo/public/ext.ts'), - Path.resolve(MOCK_REPO_DIR, 'plugins/foo/public/index.ts'), - Path.resolve(MOCK_REPO_DIR, 'plugins/foo/public/lib.ts'), - ]; - const hashes = await Hashes.ofFiles(referencedPaths); - const cacheKey = bundle.createCacheKey(referencedPaths, hashes, EMPTY_DLL_MANIFEST, []); - - bundle.cache.set({ - cacheKey, - optimizerCacheKey, - referencedPaths, - moduleCount: referencedPaths.length, - bundleRefExportIds: [], - dllRefKeys: [], - }); - - const cacheEvents = await allValuesFrom(getBundleCacheEvent$(config, optimizerCacheKey)); - - expect(cacheEvents).toMatchInlineSnapshot(` - Array [ - Object { - "bundle": , - "reason": "cache disabled", - "type": "bundle not cached", - }, - ] - `); -}); - -it('emits "bundle not cached" event when optimizerCacheKey is missing', async () => { - const config = OptimizerConfig.create({ - repoRoot: MOCK_REPO_DIR, - pluginScanDirs: [], - pluginPaths: [Path.resolve(MOCK_REPO_DIR, 'plugins/foo')], - maxWorkerCount: 1, - }); - const [bundle] = config.bundles; - - const optimizerCacheKey = 'optimizerCacheKey'; - const referencedPaths = [ - Path.resolve(MOCK_REPO_DIR, 'plugins/foo/public/ext.ts'), - Path.resolve(MOCK_REPO_DIR, 'plugins/foo/public/index.ts'), - Path.resolve(MOCK_REPO_DIR, 'plugins/foo/public/lib.ts'), - ]; - const hashes = await Hashes.ofFiles(referencedPaths); - const cacheKey = bundle.createCacheKey(referencedPaths, hashes, EMPTY_DLL_MANIFEST, []); - - bundle.cache.set({ - cacheKey, - optimizerCacheKey: undefined, - referencedPaths, - moduleCount: referencedPaths.length, - bundleRefExportIds: [], - dllRefKeys: [], - }); - - const cacheEvents = await allValuesFrom(getBundleCacheEvent$(config, optimizerCacheKey)); - - expect(cacheEvents).toMatchInlineSnapshot(` - Array [ - Object { - "bundle": , - "reason": "missing optimizer cache key", - "type": "bundle not cached", - }, - ] - `); -}); - -it('emits "bundle not cached" event when optimizerCacheKey is outdated, includes diff', async () => { - const config = OptimizerConfig.create({ - repoRoot: MOCK_REPO_DIR, - pluginScanDirs: [], - pluginPaths: [Path.resolve(MOCK_REPO_DIR, 'plugins/foo')], - maxWorkerCount: 1, - }); - const [bundle] = config.bundles; - - const optimizerCacheKey = 'optimizerCacheKey'; - const referencedPaths = [ - Path.resolve(MOCK_REPO_DIR, 'plugins/foo/public/ext.ts'), - Path.resolve(MOCK_REPO_DIR, 'plugins/foo/public/index.ts'), - Path.resolve(MOCK_REPO_DIR, 'plugins/foo/public/lib.ts'), - ]; - const hashes = await Hashes.ofFiles(referencedPaths); - const cacheKey = bundle.createCacheKey(referencedPaths, hashes, EMPTY_DLL_MANIFEST, []); - - bundle.cache.set({ - cacheKey, - optimizerCacheKey: 'old', - referencedPaths, - moduleCount: referencedPaths.length, - bundleRefExportIds: [], - dllRefKeys: [], - }); - - const cacheEvents = await allValuesFrom(getBundleCacheEvent$(config, optimizerCacheKey)); - - expect(cacheEvents).toMatchInlineSnapshot(` - Array [ - Object { - "bundle": , - "diff": "- Expected - + Received - - - \\"old\\" - + \\"optimizerCacheKey\\"", - "reason": "optimizer cache key mismatch", - "type": "bundle not cached", - }, - ] - `); -}); - -it('emits "bundle not cached" event when bundleRefExportIds is outdated, includes diff', async () => { - const config = OptimizerConfig.create({ - repoRoot: MOCK_REPO_DIR, - pluginScanDirs: [], - pluginPaths: [Path.resolve(MOCK_REPO_DIR, 'plugins/foo')], - maxWorkerCount: 1, - }); - const [bundle] = config.bundles; - - const optimizerCacheKey = 'optimizerCacheKey'; - const referencedPaths = [ - Path.resolve(MOCK_REPO_DIR, 'plugins/foo/public/ext.ts'), - Path.resolve(MOCK_REPO_DIR, 'plugins/foo/public/index.ts'), - Path.resolve(MOCK_REPO_DIR, 'plugins/foo/public/lib.ts'), - ]; - const hashes = await Hashes.ofFiles(referencedPaths); - const cacheKey = bundle.createCacheKey(referencedPaths, hashes, EMPTY_DLL_MANIFEST, []); - - bundle.cache.set({ - cacheKey, - optimizerCacheKey, - referencedPaths, - moduleCount: referencedPaths.length, - bundleRefExportIds: ['plugin/bar/public'], - dllRefKeys: [], - }); - - const cacheEvents = await allValuesFrom(getBundleCacheEvent$(config, optimizerCacheKey)); - - expect(cacheEvents).toMatchInlineSnapshot(` - Array [ - Object { - "bundle": , - "diff": "- Expected - + Received - - [ - + \\"plugin/bar/public\\" - ]", - "reason": "bundle references outdated", - "type": "bundle not cached", - }, - ] - `); -}); - -it('emits "bundle not cached" event when cacheKey is missing', async () => { - const config = OptimizerConfig.create({ - repoRoot: MOCK_REPO_DIR, - pluginScanDirs: [], - pluginPaths: [Path.resolve(MOCK_REPO_DIR, 'plugins/foo')], - maxWorkerCount: 1, - }); - const [bundle] = config.bundles; - - const optimizerCacheKey = 'optimizerCacheKey'; - const referencedPaths = [ - Path.resolve(MOCK_REPO_DIR, 'plugins/foo/public/ext.ts'), - Path.resolve(MOCK_REPO_DIR, 'plugins/foo/public/index.ts'), - Path.resolve(MOCK_REPO_DIR, 'plugins/foo/public/lib.ts'), - ]; - - bundle.cache.set({ - cacheKey: undefined, - optimizerCacheKey, - referencedPaths, - moduleCount: referencedPaths.length, - bundleRefExportIds: [], - dllRefKeys: [], - }); - - const cacheEvents = await allValuesFrom(getBundleCacheEvent$(config, optimizerCacheKey)); - - expect(cacheEvents).toMatchInlineSnapshot(` - Array [ - Object { - "bundle": , - "reason": "missing cache key", - "type": "bundle not cached", - }, - ] - `); -}); - -it('emits "bundle not cached" event when cacheKey is outdated', async () => { - const config = OptimizerConfig.create({ - repoRoot: MOCK_REPO_DIR, - pluginScanDirs: [], - pluginPaths: [Path.resolve(MOCK_REPO_DIR, 'plugins/foo')], - maxWorkerCount: 1, - }); - const [bundle] = config.bundles; - - const optimizerCacheKey = 'optimizerCacheKey'; - const referencedPaths = [ - Path.resolve(MOCK_REPO_DIR, 'plugins/foo/public/ext.ts'), - Path.resolve(MOCK_REPO_DIR, 'plugins/foo/public/index.ts'), - Path.resolve(MOCK_REPO_DIR, 'plugins/foo/public/lib.ts'), - ]; - - bundle.cache.set({ - cacheKey: 'old', - optimizerCacheKey, - referencedPaths, - moduleCount: referencedPaths.length, - bundleRefExportIds: [], - dllRefKeys: [], - }); - - jest.spyOn(bundle, 'createCacheKey').mockImplementation(() => 'new'); - - const cacheEvents = await allValuesFrom(getBundleCacheEvent$(config, optimizerCacheKey)); - - expect(cacheEvents).toMatchInlineSnapshot(` - Array [ - Object { - "bundle": , - "diff": "- Expected - + Received - - - \\"old\\" - + \\"new\\"", - "reason": "cache key mismatch", - "type": "bundle not cached", - }, - ] - `); -}); - -it('emits "dll references missing" when cacheKey has no dllRefs', async () => { - const config = OptimizerConfig.create({ - repoRoot: MOCK_REPO_DIR, - pluginScanDirs: [], - pluginPaths: [Path.resolve(MOCK_REPO_DIR, 'plugins/foo')], - maxWorkerCount: 1, - }); - const [bundle] = config.bundles; - - const optimizerCacheKey = 'optimizerCacheKey'; - const referencedPaths = [ - Path.resolve(MOCK_REPO_DIR, 'plugins/foo/public/ext.ts'), - Path.resolve(MOCK_REPO_DIR, 'plugins/foo/public/index.ts'), - Path.resolve(MOCK_REPO_DIR, 'plugins/foo/public/lib.ts'), - ]; - - bundle.cache.set({ - cacheKey: 'correct', - optimizerCacheKey, - referencedPaths, - moduleCount: referencedPaths.length, - bundleRefExportIds: [], - }); - - jest.spyOn(bundle, 'createCacheKey').mockImplementation(() => 'correct'); - - const cacheEvents = await allValuesFrom(getBundleCacheEvent$(config, optimizerCacheKey)); - - expect(cacheEvents).toMatchInlineSnapshot(` - Array [ - Object { - "bundle": , - "reason": "dll references missing", - "type": "bundle not cached", - }, - ] - `); -}); diff --git a/packages/kbn-optimizer/src/integration_tests/optimizer_built_paths.test.ts b/packages/kbn-optimizer/src/integration_tests/optimizer_built_paths.test.ts deleted file mode 100644 index 2c4f130ce9490c..00000000000000 --- a/packages/kbn-optimizer/src/integration_tests/optimizer_built_paths.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { getOptimizerBuiltPaths } from '../optimizer/optimizer_built_paths'; -import { createAbsolutePathSerializer } from '@kbn/jest-serializers'; - -expect.addSnapshotSerializer(createAbsolutePathSerializer()); - -it(`finds all the optimizer files relative to it's path`, async () => { - const paths = await getOptimizerBuiltPaths(); - expect(paths).toMatchInlineSnapshot(` - Array [ - /packages/kbn-optimizer/src/cli.ts, - /packages/kbn-optimizer/src/common/array_helpers.ts, - /packages/kbn-optimizer/src/common/bundle_cache.ts, - /packages/kbn-optimizer/src/common/bundle_refs.ts, - /packages/kbn-optimizer/src/common/bundle.ts, - /packages/kbn-optimizer/src/common/compiler_messages.ts, - /packages/kbn-optimizer/src/common/dll_manifest.ts, - /packages/kbn-optimizer/src/common/event_stream_helpers.ts, - /packages/kbn-optimizer/src/common/hashes.ts, - /packages/kbn-optimizer/src/common/index.ts, - /packages/kbn-optimizer/src/common/obj_helpers.ts, - /packages/kbn-optimizer/src/common/parse_path.ts, - /packages/kbn-optimizer/src/common/rxjs_helpers.ts, - /packages/kbn-optimizer/src/common/theme_tags.ts, - /packages/kbn-optimizer/src/common/ts_helpers.ts, - /packages/kbn-optimizer/src/common/worker_config.ts, - /packages/kbn-optimizer/src/common/worker_messages.ts, - /packages/kbn-optimizer/src/limits.ts, - /packages/kbn-optimizer/src/log_optimizer_progress.ts, - /packages/kbn-optimizer/src/log_optimizer_state.ts, - /packages/kbn-optimizer/src/optimizer/assign_bundles_to_workers.ts, - /packages/kbn-optimizer/src/optimizer/bundle_cache.ts, - /packages/kbn-optimizer/src/optimizer/diff_cache_key.ts, - /packages/kbn-optimizer/src/optimizer/filter_by_id.ts, - /packages/kbn-optimizer/src/optimizer/focus_bundles.ts, - /packages/kbn-optimizer/src/optimizer/get_plugin_bundles.ts, - /packages/kbn-optimizer/src/optimizer/handle_optimizer_completion.ts, - /packages/kbn-optimizer/src/optimizer/index.ts, - /packages/kbn-optimizer/src/optimizer/kibana_platform_plugins.ts, - /packages/kbn-optimizer/src/optimizer/observe_stdio.ts, - /packages/kbn-optimizer/src/optimizer/observe_worker.ts, - /packages/kbn-optimizer/src/optimizer/optimizer_built_paths.ts, - /packages/kbn-optimizer/src/optimizer/optimizer_cache_key.ts, - /packages/kbn-optimizer/src/optimizer/optimizer_config.ts, - /packages/kbn-optimizer/src/optimizer/optimizer_state.ts, - /packages/kbn-optimizer/src/optimizer/run_workers.ts, - /packages/kbn-optimizer/src/optimizer/watch_bundles_for_changes.ts, - /packages/kbn-optimizer/src/optimizer/watcher.ts, - /packages/kbn-optimizer/src/report_optimizer_timings.ts, - /packages/kbn-optimizer/src/run_optimizer.ts, - /packages/kbn-optimizer/src/worker/bundle_metrics_plugin.ts, - /packages/kbn-optimizer/src/worker/bundle_ref_module.ts, - /packages/kbn-optimizer/src/worker/bundle_refs_plugin.ts, - /packages/kbn-optimizer/src/worker/emit_stats_plugin.ts, - /packages/kbn-optimizer/src/worker/entry_point_creator.ts, - /packages/kbn-optimizer/src/worker/populate_bundle_cache_plugin.ts, - /packages/kbn-optimizer/src/worker/run_compilers.ts, - /packages/kbn-optimizer/src/worker/run_worker.ts, - /packages/kbn-optimizer/src/worker/theme_loader.ts, - /packages/kbn-optimizer/src/worker/webpack.config.ts, - ] - `); -}); diff --git a/packages/kbn-optimizer/src/integration_tests/watch_bundles_for_changes.test.ts b/packages/kbn-optimizer/src/integration_tests/watch_bundles_for_changes.test.ts deleted file mode 100644 index edd2b2543031c1..00000000000000 --- a/packages/kbn-optimizer/src/integration_tests/watch_bundles_for_changes.test.ts +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import * as Rx from 'rxjs'; -import { map } from 'rxjs/operators'; -import { fakeSchedulers } from 'rxjs-marbles/jest'; -import ActualWatchpack from 'watchpack'; - -import { Bundle, ascending } from '../common'; -import { watchBundlesForChanges$ } from '../optimizer/watch_bundles_for_changes'; -import { BundleCacheEvent } from '../optimizer'; - -jest.mock('fs'); -jest.mock('watchpack'); - -const MockWatchPack: jest.MockedClass = jest.requireMock('watchpack'); -const bundleEntryPath = (bundle: Bundle) => `${bundle.contextDir}/public/index.ts`; - -const makeTestBundle = (id: string) => { - const bundle = new Bundle({ - type: 'plugin', - id, - contextDir: `/repo/plugins/${id}/public`, - publicDirNames: ['public'], - outputDir: `/repo/plugins/${id}/target/public`, - sourceRoot: `/repo`, - }); - - bundle.cache.set({ - cacheKey: 'abc', - moduleCount: 1, - optimizerCacheKey: 'abc', - referencedPaths: [bundleEntryPath(bundle)], - }); - - return bundle; -}; - -const FOO_BUNDLE = makeTestBundle('foo'); -const BAR_BUNDLE = makeTestBundle('bar'); -const BAZ_BUNDLE = makeTestBundle('baz'); -const BOX_BUNDLE = makeTestBundle('box'); -const CAR_BUNDLE = makeTestBundle('car'); -const BUNDLES = [FOO_BUNDLE, BAR_BUNDLE, BAZ_BUNDLE, BOX_BUNDLE, CAR_BUNDLE]; - -const bundleCacheEvent$ = Rx.from(BUNDLES).pipe( - map( - (bundle): BundleCacheEvent => ({ - type: 'bundle cached', - bundle, - }) - ) -); - -beforeEach(async () => { - jest.useFakeTimers({ legacyFakeTimers: true }); -}); - -afterEach(async () => { - jest.useRealTimers(); -}); - -it( - 'notifies of changes and completes once all bundles have changed', - fakeSchedulers(async (advance) => { - expect.assertions(18); - - const promise = Rx.lastValueFrom( - watchBundlesForChanges$(bundleCacheEvent$, Date.now()).pipe( - map((event, i) => { - // each time we trigger a change event we get a 'changed detected' event - if (i === 0 || i === 2 || i === 4 || i === 6) { - expect(event).toHaveProperty('type', 'changes detected'); - return; - } - - expect(event).toHaveProperty('type', 'changes'); - // to teach TS what we're doing - if (event.type !== 'changes') { - return; - } - - // first we change foo and bar, after 1 second that change comes though - if (i === 1) { - expect(event.bundles).toHaveLength(2); - const [bar, foo] = event.bundles.sort(ascending((b) => b.id)); - expect(bar).toHaveProperty('id', 'bar'); - expect(foo).toHaveProperty('id', 'foo'); - } - - // next we change just the baz package and it's represented on its own - if (i === 3) { - expect(event.bundles).toHaveLength(1); - expect(event.bundles[0]).toHaveProperty('id', 'baz'); - } - - // finally we change box and car together - if (i === 5) { - expect(event.bundles).toHaveLength(2); - const [bar, foo] = event.bundles.sort(ascending((b) => b.id)); - expect(bar).toHaveProperty('id', 'box'); - expect(foo).toHaveProperty('id', 'car'); - } - }) - ) - ); - - expect(MockWatchPack.mock.instances).toHaveLength(1); - const [watcher] = MockWatchPack.mock.instances as any as Array>; - expect(watcher.on).toHaveBeenCalledTimes(1); - expect(watcher.on).toHaveBeenCalledWith('change', expect.any(Function)); - const [, changeListener] = watcher.on.mock.calls[0]; - - // foo and bar are changes without 1sec so they are batched - changeListener(bundleEntryPath(FOO_BUNDLE), 'modified'); - advance(900); - changeListener(bundleEntryPath(BAR_BUNDLE), 'modified'); - advance(1000); - - // baz is the only change in 1sec so it is on its own - changeListener(bundleEntryPath(BAZ_BUNDLE), 'modified'); - advance(1000); - - // finish by changing box and car - changeListener(bundleEntryPath(BOX_BUNDLE), 'deleted'); - changeListener(bundleEntryPath(CAR_BUNDLE), 'deleted'); - advance(1000); - - await expect(promise).resolves.toEqual(undefined); - }) -); diff --git a/packages/kbn-optimizer/src/log_optimizer_state.ts b/packages/kbn-optimizer/src/log_optimizer_state.ts index 6271ab17e1e2d8..67a35d740ebe75 100644 --- a/packages/kbn-optimizer/src/log_optimizer_state.ts +++ b/packages/kbn-optimizer/src/log_optimizer_state.ts @@ -29,7 +29,7 @@ export function logOptimizerState(log: ToolingLog, config: OptimizerConfig) { log.warning(`worker`, event.stream, event.line); } - if (event?.type === 'bundle not cached') { + if (event?.type === 'bundle not cached' && event.reason !== 'cache disabled') { log.debug( `[${event.bundle.id}] bundle not cached because [${event.reason}]${ event.diff ? `, diff:\n${event.diff}` : '' @@ -42,19 +42,24 @@ export function logOptimizerState(log: ToolingLog, config: OptimizerConfig) { } if (event?.type === 'worker started') { - let moduleCount = 0; - let workUnits = 0; - for (const bundle of event.bundles) { - moduleCount += bundle.cache.getModuleCount() ?? NaN; - workUnits += bundle.cache.getWorkUnits() ?? NaN; - } + const moduleCount = event.bundles.reduce( + (acc, b) => acc + (b.cache.getModuleCount() ?? NaN), + 0 + ); + const workUnits = event.bundles.reduce( + (acc, b) => acc + (b.cache.getWorkUnits() ?? NaN), + 0 + ); log.info( `starting worker [${event.bundles.length} ${ event.bundles.length === 1 ? 'bundle' : 'bundles' }]` ); - log.debug(`modules [${moduleCount}] work units [${workUnits}]`); + + if (moduleCount || workUnits) { + log.debug(`modules [${moduleCount || '?'}] work units [${workUnits || '?'}]`); + } } if (state.phase === 'reallocating') { @@ -65,7 +70,15 @@ export function logOptimizerState(log: ToolingLog, config: OptimizerConfig) { if (state.phase === 'initialized') { if (!loggedInit) { loggedInit = true; - log.info(`initialized, ${state.offlineBundles.length} bundles cached`); + if (config.cache) { + log.info(`initialized, ${state.offlineBundles.length} bundles cached`); + } else { + log.info('initialized'); + log.warning( + 'cache disabled, new caches will still be written but existing caches are ignored' + ); + } + if (config.themeTags.length !== ALL_THEMES.length) { log.warning( `only building [${config.themeTags}] themes, customize with the KBN_OPTIMIZER_THEMES environment variable` diff --git a/packages/kbn-optimizer/src/optimizer/assign_bundles_to_workers.test.ts b/packages/kbn-optimizer/src/optimizer/assign_bundles_to_workers.test.ts index ccf90b9e777989..d72566265a002a 100644 --- a/packages/kbn-optimizer/src/optimizer/assign_bundles_to_workers.test.ts +++ b/packages/kbn-optimizer/src/optimizer/assign_bundles_to_workers.test.ts @@ -46,11 +46,15 @@ const assertReturnVal = (workers: Assignments[]) => { const testBundle = (id: string) => new Bundle({ contextDir: `/repo/plugin/${id}/public`, - publicDirNames: ['public'], id, outputDir: `/repo/plugins/${id}/target/public`, sourceRoot: `/repo`, type: 'plugin', + remoteInfo: { + pkgId: `@kbn/${id}-plugin`, + targets: ['public'], + }, + ignoreMetrics: false, }); const getBundles = ({ diff --git a/packages/kbn-optimizer/src/optimizer/bundle_cache.ts b/packages/kbn-optimizer/src/optimizer/bundle_cache.ts index 68bfa9e4de7782..9b7299bbae0b15 100644 --- a/packages/kbn-optimizer/src/optimizer/bundle_cache.ts +++ b/packages/kbn-optimizer/src/optimizer/bundle_cache.ts @@ -12,7 +12,7 @@ import * as Rx from 'rxjs'; import { mergeAll } from 'rxjs/operators'; import { dllManifestPath } from '@kbn/ui-shared-deps-npm'; -import { Bundle, BundleRefs, Hashes, parseDllManifest } from '../common'; +import { Bundle, BundleRemotes, Hashes, parseDllManifest } from '../common'; import { OptimizerConfig } from './optimizer_config'; import { diffCacheKey } from './diff_cache_key'; @@ -46,7 +46,7 @@ export function getBundleCacheEvent$( return Rx.defer(async () => { const events: BundleCacheEvent[] = []; const eligibleBundles: Bundle[] = []; - const bundleRefs = BundleRefs.fromBundles(config.bundles); + const bundleRemotes = BundleRemotes.fromBundles(config.bundles); for (const bundle of config.filteredBundles) { if (!config.cache) { @@ -88,8 +88,8 @@ export function getBundleCacheEvent$( continue; } - const bundleRefExportIds = bundle.cache.getBundleRefExportIds(); - if (!bundleRefExportIds) { + const remoteBundleImportReqs = bundle.cache.getRemoteBundleImportReqs(); + if (!remoteBundleImportReqs) { events.push({ type: 'bundle not cached', reason: 'bundle references missing', @@ -98,16 +98,15 @@ export function getBundleCacheEvent$( continue; } - const refs = bundleRefs.filterByExportIds(bundleRefExportIds); - const bundleRefsDiff = diffCacheKey( - refs.map((r) => r.exportId).sort((a, b) => a.localeCompare(b)), - bundleRefExportIds - ); - if (bundleRefsDiff) { + const validRemoteBundleImportReqs = bundleRemotes + .unionImportReqs(remoteBundleImportReqs) + .sort((a, b) => a.localeCompare(b)); + const bundleRemotesDiff = diffCacheKey(validRemoteBundleImportReqs, remoteBundleImportReqs); + if (bundleRemotesDiff) { events.push({ type: 'bundle not cached', reason: 'bundle references outdated', - diff: bundleRefsDiff, + diff: bundleRemotesDiff, bundle, }); continue; diff --git a/packages/kbn-optimizer/src/optimizer/focus_bundles.test.ts b/packages/kbn-optimizer/src/optimizer/focus_bundles.test.ts index ba8d10f414cd80..c1c22fcf6c35cd 100644 --- a/packages/kbn-optimizer/src/optimizer/focus_bundles.test.ts +++ b/packages/kbn-optimizer/src/optimizer/focus_bundles.test.ts @@ -17,8 +17,12 @@ function createBundle(id: string, deps: ReturnType) { id, contextDir: Path.resolve('/kibana/plugins', id), outputDir: Path.resolve('/kibana/plugins', id, 'target/public'), - publicDirNames: ['public'], sourceRoot: Path.resolve('/kibana'), + remoteInfo: { + pkgId: id === 'core' ? `@kbn/core` : `@kbn/${id}-plugin`, + targets: ['public'], + }, + ignoreMetrics: false, }); jest.spyOn(bundle, 'readBundleDeps').mockReturnValue(deps); diff --git a/packages/kbn-optimizer/src/optimizer/get_plugin_bundles.test.ts b/packages/kbn-optimizer/src/optimizer/get_plugin_bundles.test.ts index fd8c6b9c37f279..9a87a9563262fd 100644 --- a/packages/kbn-optimizer/src/optimizer/get_plugin_bundles.test.ts +++ b/packages/kbn-optimizer/src/optimizer/get_plugin_bundles.test.ts @@ -24,6 +24,8 @@ it('returns a bundle for core and each plugin', () => { isUiPlugin: true, extraPublicDirs: [], manifestPath: '/repo/plugins/foo/kibana.json', + pkgId: '@kbn/foo-plugin', + ignoreMetrics: false, }, { directory: '/repo/plugins/bar', @@ -31,6 +33,8 @@ it('returns a bundle for core and each plugin', () => { isUiPlugin: false, extraPublicDirs: [], manifestPath: '/repo/plugins/bar/kibana.json', + pkgId: '@kbn/bar-plugin', + ignoreMetrics: false, }, { directory: '/outside/of/repo/plugins/baz', @@ -38,6 +42,8 @@ it('returns a bundle for core and each plugin', () => { isUiPlugin: true, extraPublicDirs: [], manifestPath: '/outside/of/repo/plugins/baz/kibana.json', + pkgId: '@kbn/external-baz-plugin', + ignoreMetrics: false, }, { directory: '/repo/x-pack/plugins/box', @@ -45,8 +51,11 @@ it('returns a bundle for core and each plugin', () => { isUiPlugin: true, extraPublicDirs: [], manifestPath: '/repo/x-pack/plugins/box/kibana.json', + pkgId: '@kbn/box-plugin', + ignoreMetrics: false, }, ], + '/repo', '/output', { @@ -61,12 +70,16 @@ it('returns a bundle for core and each plugin', () => { "banner": undefined, "contextDir": /plugins/foo, "id": "foo", + "ignoreMetrics": false, "manifestPath": /plugins/foo/kibana.json, "outputDir": /plugins/foo/target/public, "pageLoadAssetSizeLimit": undefined, - "publicDirNames": Array [ - "public", - ], + "remoteInfo": Object { + "pkgId": "@kbn/foo-plugin", + "targets": Array [ + "public", + ], + }, "sourceRoot": , "type": "plugin", }, @@ -74,27 +87,35 @@ it('returns a bundle for core and each plugin', () => { "banner": undefined, "contextDir": /plugins/baz, "id": "baz", + "ignoreMetrics": false, "manifestPath": /plugins/baz/kibana.json, "outputDir": /plugins/baz/target/public, "pageLoadAssetSizeLimit": undefined, - "publicDirNames": Array [ - "public", - ], + "remoteInfo": Object { + "pkgId": "@kbn/external-baz-plugin", + "targets": Array [ + "public", + ], + }, "sourceRoot": , "type": "plugin", }, Object { - "banner": "/*! Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one or more contributor license agreements. + "banner": "/*! Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one or more contributor license agreements. * Licensed under the Elastic License 2.0; you may not use this file except in compliance with the Elastic License 2.0. */ ", "contextDir": /x-pack/plugins/box, "id": "box", + "ignoreMetrics": false, "manifestPath": /x-pack/plugins/box/kibana.json, "outputDir": /x-pack/plugins/box/target/public, "pageLoadAssetSizeLimit": 123, - "publicDirNames": Array [ - "public", - ], + "remoteInfo": Object { + "pkgId": "@kbn/box-plugin", + "targets": Array [ + "public", + ], + }, "sourceRoot": , "type": "plugin", }, diff --git a/packages/kbn-optimizer/src/optimizer/get_plugin_bundles.ts b/packages/kbn-optimizer/src/optimizer/get_plugin_bundles.ts index 8134707561bc0e..d557c8bb6987f0 100644 --- a/packages/kbn-optimizer/src/optimizer/get_plugin_bundles.ts +++ b/packages/kbn-optimizer/src/optimizer/get_plugin_bundles.ts @@ -28,7 +28,6 @@ export function getPluginBundles( new Bundle({ type: 'plugin', id: p.id, - publicDirNames: ['public', ...p.extraPublicDirs], sourceRoot: repoRoot, contextDir: p.directory, outputDir: Path.resolve( @@ -38,10 +37,15 @@ export function getPluginBundles( ), manifestPath: p.manifestPath, banner: p.directory.startsWith(xpackDirSlash) - ? `/*! Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one or more contributor license agreements. \n` + + ? `/*! Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one or more contributor license agreements.\n` + ` * Licensed under the Elastic License 2.0; you may not use this file except in compliance with the Elastic License 2.0. */\n` : undefined, pageLoadAssetSizeLimit: limits.pageLoadAssetSize?.[p.id], + remoteInfo: { + pkgId: p.pkgId, + targets: ['public', ...p.extraPublicDirs], + }, + ignoreMetrics: p.ignoreMetrics, }) ); } diff --git a/packages/kbn-optimizer/src/optimizer/kibana_platform_plugins.test.ts b/packages/kbn-optimizer/src/optimizer/kibana_platform_plugins.test.ts deleted file mode 100644 index 2b95b5ccc5c04c..00000000000000 --- a/packages/kbn-optimizer/src/optimizer/kibana_platform_plugins.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import Path from 'path'; - -import { createAbsolutePathSerializer } from '@kbn/jest-serializers'; - -import { findKibanaPlatformPlugins } from './kibana_platform_plugins'; - -expect.addSnapshotSerializer(createAbsolutePathSerializer()); - -const FIXTURES_PATH = Path.resolve(__dirname, '../__fixtures__'); - -it('parses kibana.json files of plugins found in pluginDirs', () => { - expect( - findKibanaPlatformPlugins( - [Path.resolve(FIXTURES_PATH, 'mock_repo/plugins')], - [Path.resolve(FIXTURES_PATH, 'mock_repo/test_plugins/test_baz')] - ) - ).toMatchInlineSnapshot(` - Array [ - Object { - "directory": /packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar, - "extraPublicDirs": Array [], - "id": "bar", - "isUiPlugin": true, - "manifestPath": /packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/bar/kibana.json, - }, - Object { - "directory": /packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/foo, - "extraPublicDirs": Array [], - "id": "foo", - "isUiPlugin": true, - "manifestPath": /packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/foo/kibana.json, - }, - Object { - "directory": /packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/nested/baz, - "extraPublicDirs": Array [], - "id": "baz", - "isUiPlugin": false, - "manifestPath": /packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/nested/baz/kibana.json, - }, - Object { - "directory": /packages/kbn-optimizer/src/__fixtures__/mock_repo/test_plugins/test_baz, - "extraPublicDirs": Array [], - "id": "test_baz", - "isUiPlugin": false, - "manifestPath": /packages/kbn-optimizer/src/__fixtures__/mock_repo/test_plugins/test_baz/kibana.json, - }, - ] - `); -}); diff --git a/packages/kbn-optimizer/src/optimizer/kibana_platform_plugins.ts b/packages/kbn-optimizer/src/optimizer/kibana_platform_plugins.ts index 833d4cd8b81106..c11ed26b556f06 100644 --- a/packages/kbn-optimizer/src/optimizer/kibana_platform_plugins.ts +++ b/packages/kbn-optimizer/src/optimizer/kibana_platform_plugins.ts @@ -6,42 +6,33 @@ * Side Public License, v 1. */ -import { simpleKibanaPlatformPluginDiscovery } from '@kbn/plugin-discovery'; +import Path from 'path'; +import { type PluginPackage, getPluginPackagesFilter } from '@kbn/repo-packages'; + +const isDefaultPlugin = getPluginPackagesFilter(); export interface KibanaPlatformPlugin { readonly directory: string; readonly manifestPath: string; readonly id: string; + readonly pkgId: string; readonly isUiPlugin: boolean; readonly extraPublicDirs: string[]; + readonly ignoreMetrics: boolean; } -const isArrayOfStrings = (input: any): input is string[] => - Array.isArray(input) && input.every((p) => typeof p === 'string'); - /** * Helper to find the new platform plugins. */ -export function findKibanaPlatformPlugins(scanDirs: string[], paths: string[]) { - return simpleKibanaPlatformPluginDiscovery(scanDirs, paths).map( - ({ directory, manifestPath, manifest }): KibanaPlatformPlugin => { - let extraPublicDirs: string[] | undefined; - if (manifest.extraPublicDirs) { - if (!isArrayOfStrings(manifest.extraPublicDirs)) { - throw new TypeError( - 'expected new platform plugin manifest to have an array of strings `extraPublicDirs` property' - ); - } - extraPublicDirs = manifest.extraPublicDirs; - } - - return { - directory, - manifestPath, - id: manifest.id, - isUiPlugin: manifest.ui, - extraPublicDirs: extraPublicDirs || [], - }; - } - ); +export function toKibanaPlatformPlugin(repoRoot: string, pkg: PluginPackage): KibanaPlatformPlugin { + const directory = Path.resolve(repoRoot, pkg.normalizedRepoRelativeDir); + return { + directory, + manifestPath: Path.resolve(directory, 'kibana.jsonc'), + id: pkg.manifest.plugin.id, + pkgId: pkg.manifest.id, + isUiPlugin: pkg.isPlugin() && pkg.manifest.plugin.browser, + extraPublicDirs: (pkg.isPlugin() && pkg.manifest.plugin.extraPublicDirs) || [], + ignoreMetrics: !isDefaultPlugin(pkg), + }; } diff --git a/packages/kbn-optimizer/src/optimizer/observe_worker.ts b/packages/kbn-optimizer/src/optimizer/observe_worker.ts index cf250a7deef6e0..22674f7ca86994 100644 --- a/packages/kbn-optimizer/src/optimizer/observe_worker.ts +++ b/packages/kbn-optimizer/src/optimizer/observe_worker.ts @@ -12,7 +12,7 @@ import { fork, type ChildProcess } from 'child_process'; import * as Rx from 'rxjs'; import { map, takeUntil, first, ignoreElements } from 'rxjs/operators'; -import { isWorkerMsg, WorkerConfig, WorkerMsg, Bundle, BundleRefs } from '../common'; +import { isWorkerMsg, WorkerConfig, WorkerMsg, Bundle, BundleRemotes } from '../common'; import { observeStdio$ } from './observe_stdio'; import { OptimizerConfig } from './optimizer_config'; @@ -112,6 +112,8 @@ function initWorker( }) ); + const remotes = BundleRemotes.fromBundles(config.bundles).toSpecJson(); + return Rx.concat( msg$.pipe(first((msg) => msg === 'init')), Rx.defer(() => { @@ -119,7 +121,7 @@ function initWorker( args: [ JSON.stringify(workerConfig), JSON.stringify(bundles.map((b) => b.toSpec())), - BundleRefs.fromBundles(config.bundles).toSpecJson(), + remotes, ], }); return []; diff --git a/packages/kbn-optimizer/src/optimizer/optimizer_cache_key.test.ts b/packages/kbn-optimizer/src/optimizer/optimizer_cache_key.test.ts index 8dcaf57b23e989..f49bce8525903d 100644 --- a/packages/kbn-optimizer/src/optimizer/optimizer_cache_key.test.ts +++ b/packages/kbn-optimizer/src/optimizer/optimizer_cache_key.test.ts @@ -17,6 +17,12 @@ jest.mock('@kbn/repo-packages', () => { readHashOfPackageMap() { return ''; }, + getPackages() { + return []; + }, + getPluginPackagesFilter() { + return () => true; + }, }; }); diff --git a/packages/kbn-optimizer/src/optimizer/optimizer_config.test.ts b/packages/kbn-optimizer/src/optimizer/optimizer_config.test.ts index d6b0a2c860bc30..a29391e0acd6c3 100644 --- a/packages/kbn-optimizer/src/optimizer/optimizer_config.test.ts +++ b/packages/kbn-optimizer/src/optimizer/optimizer_config.test.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +jest.mock('@kbn/repo-packages'); jest.mock('./assign_bundles_to_workers'); jest.mock('./kibana_platform_plugins'); jest.mock('./get_plugin_bundles'); @@ -23,7 +24,6 @@ jest.mock('os', () => { }; }); -import Path from 'path'; import { REPO_ROOT } from '@kbn/repo-info'; import { createAbsolutePathSerializer } from '@kbn/jest-serializers'; @@ -46,33 +46,6 @@ describe('OptimizerConfig::parseOptions()', () => { ).toThrowErrorMatchingInlineSnapshot(`"repoRoot must be an absolute path"`); }); - it('validates that pluginScanDirs are absolute', () => { - expect(() => - OptimizerConfig.parseOptions({ - repoRoot: REPO_ROOT, - pluginScanDirs: ['foo/bar'], - }) - ).toThrowErrorMatchingInlineSnapshot(`"pluginScanDirs must all be absolute paths"`); - }); - - it('validates that pluginPaths are absolute', () => { - expect(() => - OptimizerConfig.parseOptions({ - repoRoot: REPO_ROOT, - pluginPaths: ['foo/bar'], - }) - ).toThrowErrorMatchingInlineSnapshot(`"pluginPaths must all be absolute paths"`); - }); - - it('validates that extraPluginScanDirs are absolute', () => { - expect(() => - OptimizerConfig.parseOptions({ - repoRoot: REPO_ROOT, - extraPluginScanDirs: ['foo/bar'], - }) - ).toThrowErrorMatchingInlineSnapshot(`"extraPluginScanDirs must all be absolute paths"`); - }); - it('validates that maxWorkerCount is a number', () => { expect(() => { OptimizerConfig.parseOptions({ @@ -117,13 +90,12 @@ describe('OptimizerConfig::parseOptions()', () => { "inspectWorkers": false, "maxWorkerCount": 2, "outputRoot": , - "pluginPaths": Array [], - "pluginScanDirs": Array [ - /src/plugins, - /x-pack/plugins, - /plugins, - -extra, - ], + "pluginSelector": Object { + "examples": false, + "parentDirs": undefined, + "paths": undefined, + "testPlugins": false, + }, "profileWebpack": false, "repoRoot": , "themeTags": undefined, @@ -146,13 +118,12 @@ describe('OptimizerConfig::parseOptions()', () => { "inspectWorkers": false, "maxWorkerCount": 2, "outputRoot": , - "pluginPaths": Array [], - "pluginScanDirs": Array [ - /src/plugins, - /x-pack/plugins, - /plugins, - -extra, - ], + "pluginSelector": Object { + "examples": false, + "parentDirs": undefined, + "paths": undefined, + "testPlugins": false, + }, "profileWebpack": false, "repoRoot": , "themeTags": undefined, @@ -175,15 +146,12 @@ describe('OptimizerConfig::parseOptions()', () => { "inspectWorkers": false, "maxWorkerCount": 2, "outputRoot": , - "pluginPaths": Array [], - "pluginScanDirs": Array [ - /src/plugins, - /x-pack/plugins, - /plugins, - /examples, - /x-pack/examples, - -extra, - ], + "pluginSelector": Object { + "examples": true, + "parentDirs": undefined, + "paths": undefined, + "testPlugins": false, + }, "profileWebpack": false, "repoRoot": , "themeTags": undefined, @@ -194,7 +162,6 @@ describe('OptimizerConfig::parseOptions()', () => { expect( OptimizerConfig.parseOptions({ repoRoot: REPO_ROOT, - oss: true, }) ).toMatchInlineSnapshot(` Object { @@ -206,39 +173,12 @@ describe('OptimizerConfig::parseOptions()', () => { "inspectWorkers": false, "maxWorkerCount": 2, "outputRoot": , - "pluginPaths": Array [], - "pluginScanDirs": Array [ - /src/plugins, - /plugins, - -extra, - ], - "profileWebpack": false, - "repoRoot": , - "themeTags": undefined, - "watch": false, - } - `); - - expect( - OptimizerConfig.parseOptions({ - repoRoot: REPO_ROOT, - pluginScanDirs: [Path.resolve(REPO_ROOT, 'x/y/z'), '/outside/of/repo'], - }) - ).toMatchInlineSnapshot(` - Object { - "cache": true, - "dist": false, - "filters": Array [], - "focus": Array [], - "includeCoreBundle": false, - "inspectWorkers": false, - "maxWorkerCount": 2, - "outputRoot": , - "pluginPaths": Array [], - "pluginScanDirs": Array [ - /x/y/z, - "/outside/of/repo", - ], + "pluginSelector": Object { + "examples": false, + "parentDirs": undefined, + "paths": undefined, + "testPlugins": false, + }, "profileWebpack": false, "repoRoot": , "themeTags": undefined, @@ -250,7 +190,6 @@ describe('OptimizerConfig::parseOptions()', () => { expect( OptimizerConfig.parseOptions({ repoRoot: REPO_ROOT, - pluginScanDirs: [], }) ).toMatchInlineSnapshot(` Object { @@ -262,8 +201,12 @@ describe('OptimizerConfig::parseOptions()', () => { "inspectWorkers": false, "maxWorkerCount": 100, "outputRoot": , - "pluginPaths": Array [], - "pluginScanDirs": Array [], + "pluginSelector": Object { + "examples": false, + "parentDirs": undefined, + "paths": undefined, + "testPlugins": false, + }, "profileWebpack": false, "repoRoot": , "themeTags": undefined, @@ -275,7 +218,6 @@ describe('OptimizerConfig::parseOptions()', () => { expect( OptimizerConfig.parseOptions({ repoRoot: REPO_ROOT, - pluginScanDirs: [], }) ).toMatchInlineSnapshot(` Object { @@ -287,8 +229,12 @@ describe('OptimizerConfig::parseOptions()', () => { "inspectWorkers": false, "maxWorkerCount": 100, "outputRoot": , - "pluginPaths": Array [], - "pluginScanDirs": Array [], + "pluginSelector": Object { + "examples": false, + "parentDirs": undefined, + "paths": undefined, + "testPlugins": false, + }, "profileWebpack": false, "repoRoot": , "themeTags": undefined, @@ -300,7 +246,6 @@ describe('OptimizerConfig::parseOptions()', () => { expect( OptimizerConfig.parseOptions({ repoRoot: REPO_ROOT, - pluginScanDirs: [], }) ).toMatchInlineSnapshot(` Object { @@ -312,8 +257,12 @@ describe('OptimizerConfig::parseOptions()', () => { "inspectWorkers": false, "maxWorkerCount": 100, "outputRoot": , - "pluginPaths": Array [], - "pluginScanDirs": Array [], + "pluginSelector": Object { + "examples": false, + "parentDirs": undefined, + "paths": undefined, + "testPlugins": false, + }, "profileWebpack": false, "repoRoot": , "themeTags": undefined, @@ -325,7 +274,6 @@ describe('OptimizerConfig::parseOptions()', () => { expect( OptimizerConfig.parseOptions({ repoRoot: REPO_ROOT, - pluginScanDirs: [], cache: true, }) ).toMatchInlineSnapshot(` @@ -338,8 +286,12 @@ describe('OptimizerConfig::parseOptions()', () => { "inspectWorkers": false, "maxWorkerCount": 100, "outputRoot": , - "pluginPaths": Array [], - "pluginScanDirs": Array [], + "pluginSelector": Object { + "examples": false, + "parentDirs": undefined, + "paths": undefined, + "testPlugins": false, + }, "profileWebpack": false, "repoRoot": , "themeTags": undefined, @@ -351,7 +303,6 @@ describe('OptimizerConfig::parseOptions()', () => { expect( OptimizerConfig.parseOptions({ repoRoot: REPO_ROOT, - pluginScanDirs: [], cache: true, }) ).toMatchInlineSnapshot(` @@ -364,8 +315,12 @@ describe('OptimizerConfig::parseOptions()', () => { "inspectWorkers": false, "maxWorkerCount": 100, "outputRoot": , - "pluginPaths": Array [], - "pluginScanDirs": Array [], + "pluginSelector": Object { + "examples": false, + "parentDirs": undefined, + "paths": undefined, + "testPlugins": false, + }, "profileWebpack": false, "repoRoot": , "themeTags": undefined, @@ -384,9 +339,12 @@ describe('OptimizerConfig::create()', () => { const assignBundlesToWorkers: jest.Mock = jest.requireMock( './assign_bundles_to_workers' ).assignBundlesToWorkers; - const findKibanaPlatformPlugins: jest.Mock = jest.requireMock( + const getPackages: jest.Mock = jest.requireMock('@kbn/repo-packages').getPackages; + const getPluginPackagesFilter: jest.Mock = + jest.requireMock('@kbn/repo-packages').getPluginPackagesFilter; + const toKibanaPlatformPlugin: jest.Mock = jest.requireMock( './kibana_platform_plugins' - ).findKibanaPlatformPlugins; + ).toKibanaPlatformPlugin; const getPluginBundles: jest.Mock = jest.requireMock('./get_plugin_bundles').getPluginBundles; const filterById: jest.Mock = jest.requireMock('./filter_by_id').filterById; const focusBundles: jest.Mock = jest.requireMock('./focus_bundles').focusBundles; @@ -401,7 +359,9 @@ describe('OptimizerConfig::create()', () => { { config: Symbol('worker config 1') }, { config: Symbol('worker config 2') }, ]); - findKibanaPlatformPlugins.mockReturnValue(Symbol('new platform plugins')); + getPackages.mockReturnValue([Symbol('plugin1'), Symbol('plugin2')]); + getPluginPackagesFilter.mockReturnValue(() => true); + toKibanaPlatformPlugin.mockImplementation((_, pkg) => pkg); getPluginBundles.mockReturnValue([Symbol('bundle1'), Symbol('bundle2')]); filterById.mockReturnValue(Symbol('filtered bundles')); focusBundles.mockReturnValue(Symbol('focused bundles')); @@ -414,8 +374,6 @@ describe('OptimizerConfig::create()', () => { cache: Symbol('parsed cache'), dist: Symbol('parsed dist'), maxWorkerCount: Symbol('parsed max worker count'), - pluginPaths: Symbol('parsed plugin paths'), - pluginScanDirs: Symbol('parsed plugin scan dirs'), repoRoot: Symbol('parsed repo root'), outputRoot: Symbol('parsed output root'), watch: Symbol('parsed watch'), @@ -425,6 +383,7 @@ describe('OptimizerConfig::create()', () => { filters: [], focus: [], includeCoreBundle: false, + pluginSelector: Symbol('plugin selector'), }) ); }); @@ -443,7 +402,10 @@ describe('OptimizerConfig::create()', () => { "filteredBundles": Symbol(filtered bundles), "inspectWorkers": Symbol(parsed inspect workers), "maxWorkerCount": Symbol(parsed max worker count), - "plugins": Symbol(new platform plugins), + "plugins": Array [ + Symbol(plugin1), + Symbol(plugin2), + ], "profileWebpack": Symbol(parsed profile webpack), "repoRoot": Symbol(parsed repo root), "themeTags": Symbol(theme tags), @@ -451,15 +413,6 @@ describe('OptimizerConfig::create()', () => { } `); - expect(findKibanaPlatformPlugins.mock.calls).toMatchInlineSnapshot(` - Array [ - Array [ - Symbol(parsed plugin scan dirs), - Symbol(parsed plugin paths), - ], - ] - `); - expect(filterById.mock.calls).toMatchInlineSnapshot(` Array [ Array [ @@ -472,7 +425,10 @@ describe('OptimizerConfig::create()', () => { expect(getPluginBundles.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - Symbol(new platform plugins), + Array [ + Symbol(plugin1), + Symbol(plugin2), + ], Symbol(parsed repo root), Symbol(parsed output root), Symbol(limits), diff --git a/packages/kbn-optimizer/src/optimizer/optimizer_config.ts b/packages/kbn-optimizer/src/optimizer/optimizer_config.ts index 89409b2d37680c..2af1642f3f82cf 100644 --- a/packages/kbn-optimizer/src/optimizer/optimizer_config.ts +++ b/packages/kbn-optimizer/src/optimizer/optimizer_config.ts @@ -8,7 +8,7 @@ import Path from 'path'; import Os from 'os'; -import { getPluginSearchPaths } from '@kbn/plugin-discovery'; +import { getPackages, getPluginPackagesFilter, type PluginSelector } from '@kbn/repo-packages'; import { Bundle, @@ -20,7 +20,7 @@ import { omit, } from '../common'; -import { findKibanaPlatformPlugins, KibanaPlatformPlugin } from './kibana_platform_plugins'; +import { toKibanaPlatformPlugin, KibanaPlatformPlugin } from './kibana_platform_plugins'; import { getPluginBundles } from './get_plugin_bundles'; import { filterById } from './filter_by_id'; import { focusBundles } from './focus_bundles'; @@ -64,16 +64,15 @@ interface Options { /** set to true to inspecting workers when the parent process is being inspected */ inspectWorkers?: boolean; - /** include only oss plugins in default scan dirs */ - oss?: boolean; /** include examples in default scan dirs */ examples?: boolean; - /** absolute paths to specific plugins that should be built */ + /** discover and build test plugins along with the standard plugins */ + testPlugins?: boolean; + /** absolute paths to specific plugins which should be built */ pluginPaths?: string[]; - /** absolute paths to directories that should be built, overrides the default scan dirs */ + /** absolute paths to directories, any plugins in these directories will be built */ pluginScanDirs?: string[]; - /** absolute paths that should be added to the default scan dirs */ - extraPluginScanDirs?: string[]; + /** * array of comma separated patterns that will be matched against bundle ids. * bundles will only be built if they match one of the specified patterns. @@ -86,6 +85,7 @@ interface Options { * --filter f*r # [foobar], excludes [foo, bar] */ filter?: string[]; + /** * behaves just like filter, but includes required bundles and plugins of the * listed bundle ids. Filters only apply to bundles selected by focus @@ -109,9 +109,6 @@ interface Options { /** path to a limits.yml file that should be used to inform ci-stats of metric limits */ limitsPath?: string; - - /** discover and build test plugins along with the standard plugins */ - testPlugins?: boolean; } export interface ParsedOptions { @@ -122,19 +119,17 @@ export interface ParsedOptions { profileWebpack: boolean; cache: boolean; dist: boolean; - pluginPaths: string[]; - pluginScanDirs: string[]; filters: string[]; focus: string[]; inspectWorkers: boolean; includeCoreBundle: boolean; themeTags: ThemeTags; + pluginSelector: PluginSelector; } export class OptimizerConfig { static parseOptions(options: Options): ParsedOptions { const watch = !!options.watch; - const oss = !!options.oss; const dist = !!options.dist; const examples = !!options.examples; const profileWebpack = !!options.profileWebpack; @@ -155,31 +150,6 @@ export class OptimizerConfig { throw new TypeError('outputRoot must be an absolute path'); } - const pluginScanDirs = - options.pluginScanDirs || - getPluginSearchPaths({ - rootDir: repoRoot, - oss, - examples, - testPlugins, - }); - - if (!pluginScanDirs.every((p) => Path.isAbsolute(p))) { - throw new TypeError('pluginScanDirs must all be absolute paths'); - } - - for (const extraPluginScanDir of options.extraPluginScanDirs || []) { - if (!Path.isAbsolute(extraPluginScanDir)) { - throw new TypeError('extraPluginScanDirs must all be absolute paths'); - } - pluginScanDirs.push(extraPluginScanDir); - } - - const pluginPaths = options.pluginPaths || []; - if (!pluginPaths.every((s) => Path.isAbsolute(s))) { - throw new TypeError('pluginPaths must all be absolute paths'); - } - const maxWorkerCount = process.env.KBN_OPTIMIZER_MAX_WORKERS ? parseInt(process.env.KBN_OPTIMIZER_MAX_WORKERS, 10) : options.maxWorkerCount ?? pickMaxWorkerCount(dist); @@ -191,6 +161,21 @@ export class OptimizerConfig { options.themes || (dist ? '*' : process.env.KBN_OPTIMIZER_THEMES) ); + const pluginPaths = options.pluginPaths; + if ( + pluginPaths !== undefined && + !(Array.isArray(pluginPaths) && pluginPaths.every((p) => typeof p === 'string')) + ) { + throw new TypeError('pluginPaths must be an array of strings or undefined'); + } + const pluginScanDirs = options.pluginScanDirs; + if ( + pluginScanDirs !== undefined && + !(Array.isArray(pluginScanDirs) && pluginScanDirs.every((p) => typeof p === 'string')) + ) { + throw new TypeError('pluginScanDirs must be an array of strings or undefined'); + } + return { watch, dist, @@ -199,31 +184,42 @@ export class OptimizerConfig { maxWorkerCount, profileWebpack, cache, - pluginScanDirs, - pluginPaths, filters, focus, inspectWorkers, includeCoreBundle, themeTags, + pluginSelector: { + examples, + testPlugins, + paths: pluginPaths, + parentDirs: pluginScanDirs, + }, }; } static create(inputOptions: Options) { const limits = inputOptions.limitsPath ? readLimits(inputOptions.limitsPath) : {}; const options = OptimizerConfig.parseOptions(inputOptions); - const plugins = findKibanaPlatformPlugins(options.pluginScanDirs, options.pluginPaths); + const plugins = getPackages(options.repoRoot) + .filter(getPluginPackagesFilter(options.pluginSelector)) + .map((pkg) => toKibanaPlatformPlugin(options.repoRoot, pkg)); + const bundles = [ ...(options.includeCoreBundle ? [ new Bundle({ type: 'entry', id: 'core', - publicDirNames: ['public'], sourceRoot: options.repoRoot, contextDir: Path.resolve(options.repoRoot, 'src/core'), outputDir: Path.resolve(options.outputRoot, 'src/core/target/public'), pageLoadAssetSizeLimit: limits.pageLoadAssetSize?.core, + remoteInfo: { + pkgId: '@kbn/core', + targets: ['public'], + }, + ignoreMetrics: false, }), ] : []), diff --git a/packages/kbn-optimizer/src/worker/bundle_metrics_plugin.ts b/packages/kbn-optimizer/src/worker/bundle_metrics_plugin.ts index 3e9e25e790b47a..b7a1532b271cfe 100644 --- a/packages/kbn-optimizer/src/worker/bundle_metrics_plugin.ts +++ b/packages/kbn-optimizer/src/worker/bundle_metrics_plugin.ts @@ -25,6 +25,10 @@ export class BundleMetricsPlugin { constructor(private readonly bundle: Bundle) {} public apply(compiler: webpack.Compiler) { + if (this.bundle.ignoreMetrics) { + return; + } + const { bundle } = this; compiler.hooks.emit.tap('BundleMetricsPlugin', (compilation) => { diff --git a/packages/kbn-optimizer/src/worker/bundle_refs_plugin.ts b/packages/kbn-optimizer/src/worker/bundle_refs_plugin.ts deleted file mode 100644 index 6c768a0174b51a..00000000000000 --- a/packages/kbn-optimizer/src/worker/bundle_refs_plugin.ts +++ /dev/null @@ -1,243 +0,0 @@ -/* eslint-disable @kbn/eslint/require-license-header */ - -/** - * @notice - * - * This module was heavily inspired by the externals plugin that ships with webpack@97d58d31 - * MIT License http://www.opensource.org/licenses/mit-license.php - * Author Tobias Koppers @sokra - */ - -import Path from 'path'; -import Fs from 'fs'; - -import webpack from 'webpack'; - -import { Bundle, BundleRefs, BundleRef } from '../common'; -import { BundleRefModule } from './bundle_ref_module'; - -const RESOLVE_EXTENSIONS = ['.js', '.ts', '.tsx']; - -function safeStat(path: string): Promise { - return new Promise((resolve, reject) => { - Fs.stat(path, (error, stat) => { - if (error?.code === 'ENOENT') { - resolve(undefined); - } else if (error) { - reject(error); - } else { - resolve(stat); - } - }); - }); -} - -interface RequestData { - context: string; - dependencies: Array<{ request: string }>; -} - -type Callback = (error?: any, result?: T) => void; -type ModuleFactory = (data: RequestData, callback: Callback) => void; - -export class BundleRefsPlugin { - private readonly resolvedRefEntryCache = new Map>(); - private readonly resolvedRequestCache = new Map>(); - private readonly ignorePrefix: string; - private allowedBundleIds = new Set(); - - constructor(private readonly bundle: Bundle, private readonly bundleRefs: BundleRefs) { - this.ignorePrefix = Path.resolve(this.bundle.contextDir) + Path.sep; - } - - /** - * Called by webpack when the plugin is passed in the webpack config - */ - public apply(compiler: webpack.Compiler) { - // called whenever the compiler starts to compile, passed the params - // that will be used to create the compilation - compiler.hooks.compile.tap('BundleRefsPlugin', (compilationParams: any) => { - // clear caches because a new compilation is starting, meaning that files have - // changed and we should re-run resolutions - this.resolvedRefEntryCache.clear(); - this.resolvedRequestCache.clear(); - - // hook into the creation of NormalModule instances in webpack, if the import - // statement leading to the creation of the module is pointing to a bundleRef - // entry then create a BundleRefModule instead of a NormalModule. - compilationParams.normalModuleFactory.hooks.factory.tap( - 'BundleRefsPlugin/normalModuleFactory/factory', - (wrappedFactory: ModuleFactory): ModuleFactory => - (data, callback) => { - const context = data.context; - const dep = data.dependencies[0]; - - this.maybeReplaceImport(context, dep.request).then( - (module) => { - if (!module) { - wrappedFactory(data, callback); - } else { - callback(undefined, module); - } - }, - (error) => callback(error) - ); - } - ); - }); - - compiler.hooks.compilation.tap('BundleRefsPlugin/getRequiredBundles', (compilation) => { - this.allowedBundleIds.clear(); - - const manifestPath = this.bundle.manifestPath; - if (!manifestPath) { - return; - } - - const deps = this.bundle.readBundleDeps(); - for (const ref of this.bundleRefs.forBundleIds([...deps.explicit, ...deps.implicit])) { - this.allowedBundleIds.add(ref.bundleId); - } - - compilation.hooks.additionalAssets.tap('BundleRefsPlugin/watchManifest', () => { - compilation.fileDependencies.add(manifestPath); - }); - - compilation.hooks.finishModules.tapPromise( - 'BundleRefsPlugin/finishModules', - async (modules) => { - const usedBundleIds = (modules as any[]) - .filter((m: any): m is BundleRefModule => m instanceof BundleRefModule) - .map((m) => m.ref.bundleId); - - const unusedBundleIds = deps.explicit - .filter((id) => !usedBundleIds.includes(id)) - .join(', '); - - if (unusedBundleIds) { - const error = new Error( - `Bundle for [${this.bundle.id}] lists [${unusedBundleIds}] as a required bundle, but does not use it. Please remove it.` - ); - (error as any).file = manifestPath; - compilation.errors.push(error); - } - } - ); - }); - } - - private cachedResolveRefEntry(ref: BundleRef) { - const cached = this.resolvedRefEntryCache.get(ref); - - if (cached) { - return cached; - } - - const absoluteRequest = Path.resolve(ref.contextDir, ref.entry); - const promise = this.cachedResolveRequest(absoluteRequest).then((resolved) => { - if (!resolved) { - throw new Error(`Unable to resolve request [${ref.entry}] relative to [${ref.contextDir}]`); - } - - return resolved; - }); - this.resolvedRefEntryCache.set(ref, promise); - return promise; - } - - private cachedResolveRequest(absoluteRequest: string) { - const cached = this.resolvedRequestCache.get(absoluteRequest); - - if (cached) { - return cached; - } - - const promise = this.resolveRequest(absoluteRequest); - this.resolvedRequestCache.set(absoluteRequest, promise); - return promise; - } - - private async resolveRequest(absoluteRequest: string) { - const stats = await safeStat(absoluteRequest); - if (stats && stats.isFile()) { - return absoluteRequest; - } - - // look for an index file in directories - if (stats?.isDirectory()) { - for (const ext of RESOLVE_EXTENSIONS) { - const indexPath = Path.resolve(absoluteRequest, `index${ext}`); - const indexStats = await safeStat(indexPath); - if (indexStats?.isFile()) { - return indexPath; - } - } - } - - // look for a file with one of the supported extensions - for (const ext of RESOLVE_EXTENSIONS) { - const filePath = `${absoluteRequest}${ext}`; - const fileStats = await safeStat(filePath); - if (fileStats?.isFile()) { - return filePath; - } - } - - return; - } - - /** - * Determine if an import request resolves to a bundleRef export id. If the - * request resolves to a bundle ref context but none of the exported directories - * then an error is thrown. If the request does not resolve to a bundleRef then - * undefined is returned. Otherwise it returns the referenced bundleRef. - */ - private async maybeReplaceImport(context: string, request: string) { - // ignore imports that have loaders defined or are not relative seeming - if (request.includes('!') || !request.startsWith('.')) { - return; - } - - const requestExt = Path.extname(request); - if (requestExt && !RESOLVE_EXTENSIONS.includes(requestExt)) { - return; - } - - const absoluteRequest = Path.resolve(context, request); - if (absoluteRequest.startsWith(this.ignorePrefix)) { - return; - } - - const resolved = await this.cachedResolveRequest(absoluteRequest); - if (!resolved) { - return; - } - - const possibleRefs = this.bundleRefs.filterByContextPrefix(this.bundle, resolved); - if (!possibleRefs.length) { - // import doesn't match a bundle context - return; - } - - for (const ref of possibleRefs) { - const resolvedEntry = await this.cachedResolveRefEntry(ref); - if (resolved !== resolvedEntry) { - continue; - } - - if (!this.allowedBundleIds.has(ref.bundleId)) { - throw new Error( - `import [${request}] references a public export of the [${ref.bundleId}] bundle, but that bundle is not in the "requiredPlugins" or "requiredBundles" list in the plugin manifest [${this.bundle.manifestPath}]` - ); - } - - return new BundleRefModule(ref); - } - - const bundleId = Array.from(new Set(possibleRefs.map((r) => r.bundleId))).join(', '); - const publicDir = possibleRefs.map((r) => r.entry).join(', '); - throw new Error( - `import [${request}] references a non-public export of the [${bundleId}] bundle and must point to one of the public directories: [${publicDir}]` - ); - } -} diff --git a/packages/kbn-optimizer/src/worker/bundle_ref_module.ts b/packages/kbn-optimizer/src/worker/bundle_remote_module.ts similarity index 72% rename from packages/kbn-optimizer/src/worker/bundle_ref_module.ts rename to packages/kbn-optimizer/src/worker/bundle_remote_module.ts index f7604f0f78f719..7c379b7b0d8971 100644 --- a/packages/kbn-optimizer/src/worker/bundle_ref_module.ts +++ b/packages/kbn-optimizer/src/worker/bundle_remote_module.ts @@ -8,21 +8,23 @@ * Author Tobias Koppers @sokra */ +import { KbnImportReq } from '@kbn/repo-packages'; + // @ts-ignore not typed by @types/webpack import Module from 'webpack/lib/Module'; -import { BundleRef } from '../common'; +import { BundleRemote } from '../common'; -export class BundleRefModule extends Module { +export class BundleRemoteModule extends Module { public built = false; public buildMeta?: any; public buildInfo?: any; - constructor(public readonly ref: BundleRef) { - super('kbn/bundleRef', null); + constructor(public readonly remote: BundleRemote, public readonly req: KbnImportReq) { + super('kbn/bundleRemote', null); } libIdent() { - return this.ref.exportId; + return this.req.full; } chunkCondition(chunk: any) { @@ -30,7 +32,7 @@ export class BundleRefModule extends Module { } identifier() { - return '@kbn/bundleRef ' + JSON.stringify(this.ref.exportId); + return `@kbn/bundleRemote ${this.req.full}`; } readableIdentifier() { @@ -53,7 +55,7 @@ export class BundleRefModule extends Module { source() { return ` __webpack_require__.r(__webpack_exports__); - var ns = __kbnBundles__.get('${this.ref.exportId}'); + var ns = __kbnBundles__.get('${this.remote.bundleType}/${this.remote.bundleId}/${this.req.target}'); Object.defineProperties(__webpack_exports__, Object.getOwnPropertyDescriptors(ns)) `; } diff --git a/packages/kbn-optimizer/src/worker/bundle_remotes_plugin.ts b/packages/kbn-optimizer/src/worker/bundle_remotes_plugin.ts new file mode 100644 index 00000000000000..1a5eb8b50710dd --- /dev/null +++ b/packages/kbn-optimizer/src/worker/bundle_remotes_plugin.ts @@ -0,0 +1,143 @@ +/* eslint-disable @kbn/eslint/require-license-header */ + +/** + * @notice + * + * This module was heavily inspired by the externals plugin that ships with webpack@97d58d31 + * MIT License http://www.opensource.org/licenses/mit-license.php + * Author Tobias Koppers @sokra + */ + +import webpack from 'webpack'; + +import { parseKbnImportReq } from '@kbn/repo-packages'; + +import { Bundle, BundleRemotes } from '../common'; +import { BundleRemoteModule } from './bundle_remote_module'; + +interface RequestData { + context: string; + dependencies: Array<{ request: string }>; +} + +type Callback = (error?: any, result?: T) => void; +type ModuleFactory = (data: RequestData, callback: Callback) => void; + +export class BundleRemotesPlugin { + private allowedBundleIds = new Set(); + + constructor(private readonly bundle: Bundle, private readonly remotes: BundleRemotes) {} + + /** + * Called by webpack when the plugin is passed in the webpack config + */ + public apply(compiler: webpack.Compiler) { + // called whenever the compiler starts to compile, passed the params + // that will be used to create the compilation + compiler.hooks.compile.tap('BundleRemotesPlugin', (compilationParams: any) => { + const moduleCache = new Map(); + + // hook into the creation of NormalModule instances in webpack, if the import + // statement leading to the creation of the module is pointing to a bundleRef + // entry then create a BundleRefModule instead of a NormalModule. + compilationParams.normalModuleFactory.hooks.factory.tap( + 'BundleRefsPlugin/normalModuleFactory/factory', + (wrappedFactory: ModuleFactory): ModuleFactory => + (data, callback) => { + const { request } = data.dependencies[0]; + + const cached = moduleCache.get(request); + if (cached === null) { + return wrappedFactory(data, callback); + } + if (cached !== undefined) { + return callback(null, cached); + } + + this.resolve(request, (error, result) => { + if (error || result === undefined) { + return callback(error); + } + + moduleCache.set(request, result); + + if (result === null) { + return wrappedFactory(data, callback); + } + + callback(null, result); + }); + } + ); + }); + + compiler.hooks.compilation.tap('BundleRefsPlugin/populateAllowedBundleIds', (compilation) => { + const manifestPath = this.bundle.manifestPath; + if (!manifestPath) { + return; + } + + const deps = this.bundle.readBundleDeps(); + this.allowedBundleIds = new Set([...deps.explicit, ...deps.implicit]); + + compilation.hooks.additionalAssets.tap('BundleRefsPlugin/watchManifest', () => { + compilation.fileDependencies.add(manifestPath); + }); + + compilation.hooks.finishModules.tapPromise( + 'BundleRefsPlugin/finishModules', + async (modules) => { + const usedBundleIds = (modules as any[]) + .filter((m: any): m is BundleRemoteModule => m instanceof BundleRemoteModule) + .map((m) => m.remote.bundleId); + + const unusedBundleIds = deps.explicit + .filter((id) => !usedBundleIds.includes(id)) + .join(', '); + + if (unusedBundleIds) { + const error = new Error( + `Bundle for [${this.bundle.id}] lists [${unusedBundleIds}] as a required bundle, but does not use it. Please remove it.` + ); + (error as any).file = manifestPath; + compilation.errors.push(error); + } + } + ); + }); + } + + public resolve(request: string, cb: (error?: Error, bundle?: null | BundleRemoteModule) => void) { + if (request.endsWith('.json')) { + return cb(undefined, null); + } + + const parsed = parseKbnImportReq(request); + if (!parsed) { + return cb(undefined, null); + } + + const remote = this.remotes.getForPkgId(parsed.pkgId); + if (!remote) { + return cb(undefined, null); + } + + if (!remote.targets.includes(parsed.target)) { + return cb( + new Error( + `import [${request}] references a non-public export of the [${remote.bundleId}] bundle and must point to one of the public directories: [${remote.targets}]` + ) + ); + } + + if (!this.allowedBundleIds.has(remote.bundleId)) { + return cb( + new Error( + `import [${request}] references a public export of the [${remote.bundleId}] bundle, but that bundle is not in the "requiredPlugins" or "requiredBundles" list in the plugin manifest [${this.bundle.manifestPath}]` + ) + ); + } + + return cb(undefined, new BundleRemoteModule(remote, parsed)); + } +} diff --git a/packages/kbn-optimizer/src/worker/populate_bundle_cache_plugin.ts b/packages/kbn-optimizer/src/worker/populate_bundle_cache_plugin.ts index b2e036cb7e00a0..e431e0d698d0ac 100644 --- a/packages/kbn-optimizer/src/worker/populate_bundle_cache_plugin.ts +++ b/packages/kbn-optimizer/src/worker/populate_bundle_cache_plugin.ts @@ -27,7 +27,7 @@ import { Hashes, ParsedDllManifest, } from '../common'; -import { BundleRefModule } from './bundle_ref_module'; +import { BundleRemoteModule } from './bundle_remote_module'; /** * sass-loader creates about a 40% overhead on the overall optimizer runtime, and @@ -88,6 +88,7 @@ export class PopulateBundleCachePlugin { const path = getModulePath(module); const parsedPath = parseFilePath(path); + // TODO: Does this need to be updated to support @kbn/ packages? if (!parsedPath.dirs.includes('node_modules')) { addReferenced(path); @@ -113,8 +114,8 @@ export class PopulateBundleCachePlugin { continue; } - if (module instanceof BundleRefModule) { - bundleRefExportIds.push(module.ref.exportId); + if (module instanceof BundleRemoteModule) { + bundleRefExportIds.push(module.req.full); continue; } @@ -140,7 +141,7 @@ export class PopulateBundleCachePlugin { const sortedDllRefKeys = Array.from(dllRefKeys).sort(ascending((p) => p)); bundle.cache.set({ - bundleRefExportIds: bundleRefExportIds.sort(ascending((p) => p)), + remoteBundleImportReqs: bundleRefExportIds.sort(ascending((p) => p)), optimizerCacheKey: workerConfig.optimizerCacheKey, cacheKey: bundle.createCacheKey( referencedPaths, diff --git a/packages/kbn-optimizer/src/worker/run_compilers.ts b/packages/kbn-optimizer/src/worker/run_compilers.ts index ca69e195000ed1..4dd8f23bd1bdf4 100644 --- a/packages/kbn-optimizer/src/worker/run_compilers.ts +++ b/packages/kbn-optimizer/src/worker/run_compilers.ts @@ -13,7 +13,14 @@ import * as Rx from 'rxjs'; import { mergeMap, map, mapTo, takeUntil } from 'rxjs/operators'; import { isFailureStats, failedStatsToErrorMessage } from '@kbn/optimizer-webpack-helpers'; -import { CompilerMsgs, CompilerMsg, maybeMap, Bundle, WorkerConfig, BundleRefs } from '../common'; +import { + CompilerMsgs, + CompilerMsg, + maybeMap, + Bundle, + WorkerConfig, + BundleRemotes, +} from '../common'; import { getWebpackConfig } from './webpack.config'; const PLUGIN_NAME = '@kbn/optimizer'; @@ -97,10 +104,10 @@ const observeCompiler = ( export const runCompilers = ( workerConfig: WorkerConfig, bundles: Bundle[], - bundleRefs: BundleRefs + bundleRemotes: BundleRemotes ) => { const multiCompiler = webpack( - bundles.map((def) => getWebpackConfig(def, bundleRefs, workerConfig)) + bundles.map((def) => getWebpackConfig(def, bundleRemotes, workerConfig)) ); return Rx.merge( diff --git a/packages/kbn-optimizer/src/worker/run_worker.ts b/packages/kbn-optimizer/src/worker/run_worker.ts index 209997acbf3092..30f44724357319 100644 --- a/packages/kbn-optimizer/src/worker/run_worker.ts +++ b/packages/kbn-optimizer/src/worker/run_worker.ts @@ -17,7 +17,7 @@ import { WorkerMsg, isWorkerMsg, WorkerMsgs, - BundleRefs, + BundleRemotes, } from '../common'; import { runCompilers } from './run_compilers'; @@ -96,7 +96,7 @@ Rx.defer(() => { const workerConfig = parseWorkerConfig(msg.args[0]); const bundles = parseBundles(msg.args[1]); - const bundleRefs = BundleRefs.parseSpec(msg.args[2]); + const bundleRefs = BundleRemotes.parseSpec(msg.args[2]); // set BROWSERSLIST_ENV so that style/babel loaders see it before running compilers process.env.BROWSERSLIST_ENV = workerConfig.browserslistEnv; diff --git a/packages/kbn-optimizer/src/worker/webpack.config.ts b/packages/kbn-optimizer/src/worker/webpack.config.ts index 7a3c9765112e63..986ec051637e43 100644 --- a/packages/kbn-optimizer/src/worker/webpack.config.ts +++ b/packages/kbn-optimizer/src/worker/webpack.config.ts @@ -19,29 +19,20 @@ import CompressionPlugin from 'compression-webpack-plugin'; import UiSharedDepsNpm from '@kbn/ui-shared-deps-npm'; import * as UiSharedDepsSrc from '@kbn/ui-shared-deps-src'; -import { Bundle, BundleRefs, WorkerConfig, parseDllManifest } from '../common'; -import { BundleRefsPlugin } from './bundle_refs_plugin'; +import { Bundle, BundleRemotes, WorkerConfig, parseDllManifest } from '../common'; +import { BundleRemotesPlugin } from './bundle_remotes_plugin'; import { BundleMetricsPlugin } from './bundle_metrics_plugin'; import { EmitStatsPlugin } from './emit_stats_plugin'; import { PopulateBundleCachePlugin } from './populate_bundle_cache_plugin'; -const BABEL_PRESET = [ - require.resolve('@kbn/babel-preset/webpack_preset'), - { - 'kibana/ignoredPkgIds': Object.keys(UiSharedDepsSrc.externals), - }, -]; +const BABEL_PRESET = require.resolve('@kbn/babel-preset/webpack_preset'); const DLL_MANIFEST = JSON.parse(Fs.readFileSync(UiSharedDepsNpm.dllManifestPath, 'utf8')); -const nodeModulesButNotKbnPackages = (path: string) => { - if (!path.includes('node_modules')) { - return false; - } - - return !path.includes(`node_modules${Path.sep}@kbn${Path.sep}`); -}; - -export function getWebpackConfig(bundle: Bundle, bundleRefs: BundleRefs, worker: WorkerConfig) { +export function getWebpackConfig( + bundle: Bundle, + bundleRemotes: BundleRemotes, + worker: WorkerConfig +) { const ENTRY_CREATOR = require.resolve('./entry_point_creator'); const commonConfig: webpack.Configuration = { @@ -83,7 +74,7 @@ export function getWebpackConfig(bundle: Bundle, bundleRefs: BundleRefs, worker: plugins: [ new CleanWebpackPlugin(), - new BundleRefsPlugin(bundle, bundleRefs), + new BundleRemotesPlugin(bundle, bundleRemotes), new PopulateBundleCachePlugin(worker, bundle, parseDllManifest(DLL_MANIFEST)), new BundleMetricsPlugin(bundle), new webpack.DllReferencePlugin({ @@ -116,10 +107,10 @@ export function getWebpackConfig(bundle: Bundle, bundleRefs: BundleRefs, worker: { loader: require.resolve('val-loader'), options: { - entries: bundle.publicDirNames.map((name) => { - const absolute = Path.resolve(bundle.contextDir, name); + entries: bundle.remoteInfo.targets.map((target) => { + const absolute = Path.resolve(bundle.contextDir, target); const newContext = Path.dirname(ENTRY_CREATOR); - const importId = `${bundle.type}/${bundle.id}/${name}`; + const importId = `${bundle.type}/${bundle.id}/${target}`; // relative path from context of the ENTRY_CREATOR, with linux path separators let requirePath = Path.relative(newContext, absolute).split('\\').join('/'); @@ -160,7 +151,7 @@ export function getWebpackConfig(bundle: Bundle, bundleRefs: BundleRefs, worker: }, { test: /\.scss$/, - exclude: nodeModulesButNotKbnPackages, + exclude: /node_modules/, oneOf: [ ...worker.themeTags.map((theme) => ({ resourceQuery: `?${theme}`, @@ -257,7 +248,6 @@ export function getWebpackConfig(bundle: Bundle, bundleRefs: BundleRefs, worker: ), vega: Path.resolve(worker.repoRoot, 'node_modules/vega/build-es5/vega.js'), }, - symlinks: false, }, performance: { diff --git a/packages/kbn-optimizer/tsconfig.json b/packages/kbn-optimizer/tsconfig.json index e47ae96a1df583..62e191e3413d4e 100644 --- a/packages/kbn-optimizer/tsconfig.json +++ b/packages/kbn-optimizer/tsconfig.json @@ -27,7 +27,6 @@ "@kbn/repo-info", "@kbn/dev-cli-runner", "@kbn/jest-serializers", - "@kbn/plugin-discovery", "@kbn/repo-packages", ] } diff --git a/packages/kbn-plugin-discovery/README.mdx b/packages/kbn-plugin-discovery/README.mdx deleted file mode 100644 index 97689cd9458d45..00000000000000 --- a/packages/kbn-plugin-discovery/README.mdx +++ /dev/null @@ -1,32 +0,0 @@ ---- -id: kibDevDocsOpsPluginDiscovery -slug: /kibana-dev-docs/ops/plugin-discovery -title: "@kbn/plugin-discovery" -description: A package with logic used to find plugins in the repository -date: 2022-06-06 -tags: ['kibana', 'dev', 'contributor', 'operations', 'plugin', 'discovery'] ---- - -At the moment plugins can live in a couple of different places and in the future will be able -to live anywhere in the repository. This is a package that holds custom logic useful to find and -parse those. - -## API - -### parseKibanaPlatformPlugin - -It returns a platform plugin for a given manifest path - -### getPluginSearchPaths - -It returns the paths where plugins will be searched for - -### simpleKibanaPlatformPluginDiscovery - -It finds and returns the new platform plugins - -## NOTE: - -This code is needed in order to properly bootstrap the repository. As such, it can't have any NPM dependencies or require being built. This code is loaded directly into the node.js process that boostraps the repository from source while also being built into a package and exposed to the rest of the package system. Please consider this when making any changes to the source. - -The code is still type-checked as JS with JSDoc comments, and a single .ts file which provides interfaces to the JS validation and are publically available to package consumers. \ No newline at end of file diff --git a/packages/kbn-plugin-discovery/index.js b/packages/kbn-plugin-discovery/index.js deleted file mode 100644 index a88ae4dc8d0d65..00000000000000 --- a/packages/kbn-plugin-discovery/index.js +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -/** @typedef {import('./src/types').KibanaPlatformPlugin} KibanaPlatformPlugin */ - -const { parseKibanaPlatformPlugin } = require('./src/parse_kibana_platform_plugin'); -const { getPluginSearchPaths } = require('./src/plugin_search_paths'); -const { - simpleKibanaPlatformPluginDiscovery, -} = require('./src/simple_kibana_platform_plugin_discovery'); - -module.exports = { - parseKibanaPlatformPlugin, - getPluginSearchPaths, - simpleKibanaPlatformPluginDiscovery, -}; diff --git a/packages/kbn-plugin-discovery/kibana.jsonc b/packages/kbn-plugin-discovery/kibana.jsonc deleted file mode 100644 index d14b8a8eadb99e..00000000000000 --- a/packages/kbn-plugin-discovery/kibana.jsonc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "type": "shared-common", - "id": "@kbn/plugin-discovery", - "owner": "@elastic/kibana-operations" -} diff --git a/packages/kbn-plugin-discovery/package.json b/packages/kbn-plugin-discovery/package.json deleted file mode 100644 index 359dd420d87e30..00000000000000 --- a/packages/kbn-plugin-discovery/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "@kbn/plugin-discovery", - "private": true, - "version": "1.0.0", - "license": "SSPL-1.0 OR Elastic License 2.0" -} \ No newline at end of file diff --git a/packages/kbn-plugin-discovery/src/find_files.js b/packages/kbn-plugin-discovery/src/find_files.js deleted file mode 100644 index 6f9e09cbfa3d1d..00000000000000 --- a/packages/kbn-plugin-discovery/src/find_files.js +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -const Path = require('path'); -const Fs = require('fs'); - -/** - * @param {string} path - */ -function safeReadDir(path) { - try { - return Fs.readdirSync(path, { - withFileTypes: true, - }); - } catch (error) { - if (error.code === 'ENOENT' || error.code === 'ENOTDIR') { - return []; - } - - throw error; - } -} - -/** - * Search for files named `name` in `dir`, up to `depth` levels deep. If a directory has a - * matching file its children are not iterated, otherwise if depth > 0 then all child - * directories are checked recursively with depth-1 - * - * @param {string} dir - * @param {number} depth - * @param {string} name - * @returns {string[]} - */ -function findFiles(dir, depth, name) { - // if depth = 0 then we just need to determine if there is a kibana.json file in this directory - // and return either that path or an empty array - if (depth === 0) { - const path = Path.resolve(dir, name); - return Fs.existsSync(path) ? [path] : []; - } - - // if depth > 0 read the files in this directory, if we find a kibana.json file then we can stop - // otherwise we will iterate through the child directories and try to find kibana.json files there. - const files = safeReadDir(dir); - - /** @type {string[]} */ - const childDirs = []; - for (const ent of files) { - if (ent.isFile()) { - if (ent.name === name) { - return [Path.resolve(dir, ent.name)]; - } - } else if (ent.isDirectory()) { - childDirs.push(Path.resolve(dir, ent.name)); - } - } - - return childDirs.flatMap((dir) => findFiles(dir, depth - 1, name)); -} - -module.exports = { findFiles }; diff --git a/packages/kbn-plugin-discovery/src/load_json_file.js b/packages/kbn-plugin-discovery/src/load_json_file.js deleted file mode 100644 index 2e35a503ccf4cc..00000000000000 --- a/packages/kbn-plugin-discovery/src/load_json_file.js +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -const Fs = require('fs'); -const Path = require('path'); - -/** - * @param {string} path - * @returns {any} - */ -function loadJsonFile(path) { - try { - return JSON.parse(Fs.readFileSync(path, 'utf8')); - } catch (error) { - if (error.code === 'ENOENT') { - throw new Error(`Missing file: ${path}`); - } - - throw new Error( - `Unable to read JSON at [${Path.relative(process.cwd(), path)}]: ${error.message}` - ); - } -} - -module.exports = { loadJsonFile }; diff --git a/packages/kbn-plugin-discovery/src/parse_kibana_platform_plugin.js b/packages/kbn-plugin-discovery/src/parse_kibana_platform_plugin.js deleted file mode 100644 index 6137d5ad401873..00000000000000 --- a/packages/kbn-plugin-discovery/src/parse_kibana_platform_plugin.js +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -const Path = require('path'); - -const { loadJsonFile } = require('./load_json_file'); - -/** - * @param {unknown} input - * @param {string} type - * @returns {string[]} - */ -function isValidDepsDeclaration(input, type) { - if (typeof input === 'undefined') return []; - if (Array.isArray(input) && input.every((i) => typeof i === 'string')) { - return input; - } - throw new TypeError(`The "${type}" in plugin manifest should be an array of strings.`); -} - -/** - * @param {string} manifestPath - * @returns {import('./types').KibanaPlatformPlugin} - */ -function parseKibanaPlatformPlugin(manifestPath) { - if (!Path.isAbsolute(manifestPath)) { - throw new TypeError('expected new platform manifest path to be absolute'); - } - - /** @type {Partial} */ - const manifest = loadJsonFile(manifestPath); - if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest)) { - throw new TypeError('expected new platform plugin manifest to be a JSON encoded object'); - } - - if (typeof manifest.id !== 'string') { - throw new TypeError('expected new platform plugin manifest to have a string id'); - } - - if (typeof manifest.version !== 'string') { - throw new TypeError('expected new platform plugin manifest to have a string version'); - } - - if (!manifest.owner || typeof manifest.owner.name !== 'string') { - throw new TypeError( - `Expected plugin ${manifest.id} manifest to have an owner with name specified (${manifestPath})` - ); - } - - return { - directory: Path.dirname(manifestPath), - manifestPath, - manifest: { - ...manifest, - - ui: !!manifest.ui, - server: !!manifest.server, - id: manifest.id, - version: manifest.version, - kibanaVersion: manifest.kibanaVersion || manifest.version, - serviceFolders: manifest.serviceFolders || [], - owner: manifest.owner, - description: manifest.description, - enabledOnAnonymousPages: Boolean(manifest.enabledOnAnonymousPages), - requiredPlugins: isValidDepsDeclaration(manifest.requiredPlugins, 'requiredPlugins'), - optionalPlugins: isValidDepsDeclaration(manifest.optionalPlugins, 'optionalPlugins'), - requiredBundles: isValidDepsDeclaration(manifest.requiredBundles, 'requiredBundles'), - extraPublicDirs: isValidDepsDeclaration(manifest.extraPublicDirs, 'extraPublicDirs'), - }, - }; -} - -module.exports = { parseKibanaPlatformPlugin }; diff --git a/packages/kbn-plugin-discovery/src/plugin_search_paths.js b/packages/kbn-plugin-discovery/src/plugin_search_paths.js deleted file mode 100644 index ed702cf8e79c22..00000000000000 --- a/packages/kbn-plugin-discovery/src/plugin_search_paths.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -const { resolve } = require('path'); - -/** - * @param {{ rootDir: string; oss: boolean; examples: boolean; testPlugins?: boolean; }} options - * @returns {string[]} - */ -function getPluginSearchPaths({ rootDir, oss, examples, testPlugins }) { - return [ - resolve(rootDir, 'src', 'plugins'), - ...(oss ? [] : [resolve(rootDir, 'x-pack', 'plugins')]), - resolve(rootDir, 'plugins'), - ...(examples ? [resolve(rootDir, 'examples')] : []), - ...(examples && !oss ? [resolve(rootDir, 'x-pack', 'examples')] : []), - resolve(rootDir, '..', 'kibana-extra'), - ...(testPlugins - ? [ - resolve(rootDir, 'test/analytics/plugins'), - resolve(rootDir, 'test/health_gateway/plugins'), - resolve(rootDir, 'test/plugin_functional/plugins'), - resolve(rootDir, 'test/interpreter_functional/plugins'), - resolve(rootDir, 'test/common/plugins'), - resolve(rootDir, 'test/server_integration/plugins'), - ] - : []), - ...(testPlugins && !oss - ? [ - resolve(rootDir, 'x-pack/test/plugin_functional/plugins'), - resolve(rootDir, 'x-pack/test/functional_with_es_ssl/plugins'), - resolve(rootDir, 'x-pack/test/alerting_api_integration/plugins'), - resolve(rootDir, 'x-pack/test/plugin_api_integration/plugins'), - resolve(rootDir, 'x-pack/test/plugin_api_perf/plugins'), - resolve(rootDir, 'x-pack/test/licensing_plugin/plugins'), - resolve(rootDir, 'x-pack/test/usage_collection/plugins'), - resolve(rootDir, 'x-pack/test/security_functional/plugins'), - ] - : []), - ]; -} - -module.exports = { getPluginSearchPaths }; diff --git a/packages/kbn-plugin-discovery/src/simple_kibana_platform_plugin_discovery.js b/packages/kbn-plugin-discovery/src/simple_kibana_platform_plugin_discovery.js deleted file mode 100644 index 8d5da4e5193551..00000000000000 --- a/packages/kbn-plugin-discovery/src/simple_kibana_platform_plugin_discovery.js +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -const { parseKibanaPlatformPlugin } = require('./parse_kibana_platform_plugin'); -const { findFiles } = require('./find_files'); - -/** - * Helper to find the new platform plugins. - * @param {string[]} scanDirs - * @param {string[]} pluginPaths - * @returns {Array} - */ -function simpleKibanaPlatformPluginDiscovery(scanDirs, pluginPaths) { - return Array.from( - new Set([ - // find kibana.json files up to 5 levels within each scan dir - ...scanDirs.flatMap((dir) => findFiles(dir, 5, 'kibana.json')), - // find kibana.json files at the root of each plugin path - ...pluginPaths.flatMap((path) => findFiles(path, 0, 'kibana.json')), - ]) - ).map(parseKibanaPlatformPlugin); -} - -module.exports = { simpleKibanaPlatformPluginDiscovery }; diff --git a/packages/kbn-plugin-discovery/src/types.ts b/packages/kbn-plugin-discovery/src/types.ts deleted file mode 100644 index ebf3f68074c7bf..00000000000000 --- a/packages/kbn-plugin-discovery/src/types.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export type JsonValue = - | { [key: string]: JsonValue } - | JsonValue[] - | string - | number - | boolean - | null; - -export interface KibanaPlatformPlugin { - readonly directory: string; - readonly manifestPath: string; - readonly manifest: KibanaPlatformPluginManifest; -} - -export interface KibanaPlatformPluginManifest { - id: string; - ui: boolean; - server: boolean; - kibanaVersion: string; - version: string; - owner: { - // Internally, this should be a team name. - name: string; - // All internally owned plugins should have a github team specified that can be pinged in issues, or used to look up - // members who can be asked questions regarding the plugin. - githubTeam?: string; - }; - // TODO: make required. - description?: string; - enabledOnAnonymousPages?: boolean; - serviceFolders: readonly string[]; - requiredPlugins: readonly string[]; - optionalPlugins: readonly string[]; - requiredBundles: readonly string[]; - extraPublicDirs: readonly string[]; -} diff --git a/packages/kbn-plugin-generator/src/ask_questions.ts b/packages/kbn-plugin-generator/src/ask_questions.ts index 96aaec218e64f9..ea59fc2df152be 100644 --- a/packages/kbn-plugin-generator/src/ask_questions.ts +++ b/packages/kbn-plugin-generator/src/ask_questions.ts @@ -57,38 +57,12 @@ export const QUESTIONS = [ message: 'Provide a description for your plugin.', default: undefined, }, - { - 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: 'ownerName', message: 'Who is developing and maintaining this plugin?', default: undefined, when: ({ internal }: Answers) => !internal, }, - { - name: 'ownerName', - message: 'What team will maintain this plugin?', - default: undefined, - when: ({ internal }: Answers) => internal, - }, - { - name: 'githubTeam', - message: 'What is your gitHub team alias?', - default: undefined, - when: ({ internal }: Answers) => internal, - }, { name: 'ui', type: 'confirm', diff --git a/packages/kbn-plugin-generator/template/public/index.ts.ejs b/packages/kbn-plugin-generator/template/public/index.ts.ejs index f859f34bee2eea..55227214d05e98 100644 --- a/packages/kbn-plugin-generator/template/public/index.ts.ejs +++ b/packages/kbn-plugin-generator/template/public/index.ts.ejs @@ -7,7 +7,7 @@ import { <%= upperCamelCase(name) %>Plugin } from './plugin'; export function plugin() { return new <%= upperCamelCase(name) %>Plugin(); } -export { +export type { <%= upperCamelCase(name) %>PluginSetup, <%= upperCamelCase(name) %>PluginStart, } from './types'; diff --git a/packages/kbn-plugin-generator/template/server/index.ts.ejs b/packages/kbn-plugin-generator/template/server/index.ts.ejs index f6b40f2ee06426..5e0510b7ecb86e 100644 --- a/packages/kbn-plugin-generator/template/server/index.ts.ejs +++ b/packages/kbn-plugin-generator/template/server/index.ts.ejs @@ -9,7 +9,7 @@ import { <%= upperCamelCase(name) %>Plugin } from './plugin'; return new <%= upperCamelCase(name) %>Plugin(initializerContext); } -export { +export type { <%= upperCamelCase(name) %>PluginSetup, <%= upperCamelCase(name) %>PluginStart, } from './types'; diff --git a/packages/kbn-plugin-helpers/src/cli.ts b/packages/kbn-plugin-helpers/src/cli.ts index 7109f3aa2ebc02..3115c014438a5d 100644 --- a/packages/kbn-plugin-helpers/src/cli.ts +++ b/packages/kbn-plugin-helpers/src/cli.ts @@ -11,7 +11,7 @@ import Path from 'path'; import { RunWithCommands } from '@kbn/dev-cli-runner'; import { createFlagError, createFailError } from '@kbn/dev-cli-errors'; -import { findKibanaJson } from './find_kibana_json'; +import { findPluginDir } from './find_plugin_dir'; import { loadKibanaPlatformPlugin } from './load_kibana_platform_plugin'; import * as Tasks from './tasks'; import { BuildContext } from './build_context'; @@ -56,14 +56,18 @@ export function runCli() { throw createFlagError('expected a single --skip-archive flag'); } - const pluginDir = await findKibanaJson(process.cwd()); - if (!pluginDir) { + const found = await findPluginDir(); + if (!found) { throw createFailError( `Unable to find Kibana Platform plugin in [${process.cwd()}] or any of its parent directories. Has it been migrated properly? Does it have a kibana.json file?` ); } - const plugin = loadKibanaPlatformPlugin(pluginDir); + if (found.type === 'package') { + throw createFailError(`the plugin helpers do not currently support "package plugins"`); + } + + const plugin = loadKibanaPlatformPlugin(found.dir); const config = await loadConfig(log, plugin); const kibanaVersion = await resolveKibanaVersion(versionFlag, plugin); const sourceDir = plugin.directory; diff --git a/packages/kbn-plugin-helpers/src/find_kibana_json.ts b/packages/kbn-plugin-helpers/src/find_kibana_json.ts deleted file mode 100644 index 97fdb513421c27..00000000000000 --- a/packages/kbn-plugin-helpers/src/find_kibana_json.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import Path from 'path'; -import Fs from 'fs'; -import { promisify } from 'util'; - -const existsAsync = promisify(Fs.exists); - -export async function findKibanaJson(directory: string): Promise { - if (await existsAsync(Path.resolve(directory, 'kibana.json'))) { - return directory; - } - - const parent = Path.dirname(directory); - if (parent === directory) { - return undefined; - } - - return findKibanaJson(parent); -} diff --git a/packages/kbn-plugin-helpers/src/find_plugin_dir.ts b/packages/kbn-plugin-helpers/src/find_plugin_dir.ts new file mode 100644 index 00000000000000..8ff40c56e9614f --- /dev/null +++ b/packages/kbn-plugin-helpers/src/find_plugin_dir.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import Path from 'path'; +import Fs from 'fs'; + +export function findPluginDir( + from = process.cwd() +): { dir: string; type: 'legacy' | 'package' } | undefined { + if (Fs.existsSync(Path.resolve(from, 'kibana.json'))) { + return { + dir: from, + type: 'legacy', + }; + } + + if (Fs.existsSync(Path.resolve(from, 'kibana.jsonc'))) { + return { + dir: from, + type: 'package', + }; + } + + const parent = Path.dirname(from); + if (parent === from) { + return undefined; + } + + return findPluginDir(parent); +} diff --git a/packages/kbn-plugin-helpers/src/integration_tests/build.test.ts b/packages/kbn-plugin-helpers/src/integration_tests/build.test.ts index bae49182aab102..2a4b57e3bdfcc2 100644 --- a/packages/kbn-plugin-helpers/src/integration_tests/build.test.ts +++ b/packages/kbn-plugin-helpers/src/integration_tests/build.test.ts @@ -69,13 +69,12 @@ it('builds a generated plugin into a viable archive', async () => { expect(filterLogs(buildProc.all)).toMatchInlineSnapshot(` " info deleting the build and target directories info running @kbn/optimizer - │ info initialized, 0 bundles cached - │ info starting worker [1 bundle] - │ succ 1 bundles compiled successfully after