diff --git a/src/dev/typescript/build_ts_refs.ts b/src/dev/typescript/build_ts_refs.ts index 26425b7a3e61d..b90adc7c3e1a1 100644 --- a/src/dev/typescript/build_ts_refs.ts +++ b/src/dev/typescript/build_ts_refs.ts @@ -11,7 +11,7 @@ import Path from 'path'; import execa from 'execa'; import { ToolingLog, REPO_ROOT } from '@kbn/dev-utils'; -export const REF_CONFIG_PATHS = [Path.resolve(REPO_ROOT, 'tsconfig.refs.json')]; +import { REF_CONFIG_PATHS } from './root_refs_config'; export async function buildAllTsRefs(log: ToolingLog): Promise<{ failed: boolean }> { for (const path of REF_CONFIG_PATHS) { diff --git a/src/dev/typescript/build_ts_refs_cli.ts b/src/dev/typescript/build_ts_refs_cli.ts index ad7807eb87c66..ace2ea5e2adbd 100644 --- a/src/dev/typescript/build_ts_refs_cli.ts +++ b/src/dev/typescript/build_ts_refs_cli.ts @@ -12,7 +12,8 @@ import { run, REPO_ROOT } from '@kbn/dev-utils'; import del from 'del'; import { RefOutputCache } from './ref_output_cache'; -import { buildAllTsRefs, REF_CONFIG_PATHS } from './build_ts_refs'; +import { buildAllTsRefs } from './build_ts_refs'; +import { updateRootRefsConfig, validateRootRefsConfig, REF_CONFIG_PATHS } from './root_refs_config'; import { getOutputsDeep } from './ts_configfile'; import { concurrentMap } from './concurrent_map'; @@ -36,6 +37,12 @@ export async function runBuildRefsCli() { return; } + if (!!flags['validate-root-refs-config']) { + await validateRootRefsConfig(log); + } else { + await updateRootRefsConfig(log); + } + const outDirs = getOutputsDeep(REF_CONFIG_PATHS); const cacheEnabled = process.env.BUILD_TS_REFS_CACHE_ENABLE !== 'false' && !!flags.cache; @@ -89,7 +96,7 @@ export async function runBuildRefsCli() { { description: 'Build TypeScript projects', flags: { - boolean: ['clean', 'force', 'cache', 'ignore-type-failures'], + boolean: ['clean', 'force', 'cache', 'ignore-type-failures', 'validate-root-refs-config'], default: { cache: true, }, @@ -97,7 +104,9 @@ export async function runBuildRefsCli() { --force Run the build even if the BUILD_TS_REFS_DISABLE is set to "true" --clean Delete outDirs for each ts project before building --no-cache Disable fetching/extracting outDir caches based on the mergeBase with upstream - --ignore-type-failures If tsc reports type errors, ignore them and just log a small warning. + --ignore-type-failures If tsc reports type errors, ignore them and just log a small warning + --validate-root-refs-config Ensure that all necessary files are listed in the root tsconfig.refs.json + file, and if not fail with an error message about how to update it `, }, log: { diff --git a/src/dev/typescript/project.ts b/src/dev/typescript/project.ts index 8d92284e49637..893446eadd145 100644 --- a/src/dev/typescript/project.ts +++ b/src/dev/typescript/project.ts @@ -63,4 +63,12 @@ export class Project { public isAbsolutePathSelected(path: string) { return testMatchers(this.exclude, path) ? false : testMatchers(this.include, path); } + + public isCompositeProject() { + return !!this.config.compilerOptions?.composite; + } + + public getRefdPaths() { + return this.config.references?.map((r: { path: string }) => r.path) ?? []; + } } diff --git a/src/dev/typescript/root_refs_config.ts b/src/dev/typescript/root_refs_config.ts new file mode 100644 index 0000000000000..c156f11b8b726 --- /dev/null +++ b/src/dev/typescript/root_refs_config.ts @@ -0,0 +1,114 @@ +/* + * 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/promises'; + +import { REPO_ROOT, ToolingLog, createFailError } from '@kbn/dev-utils'; +import dedent from 'dedent'; + +import { PROJECTS } from './projects'; +import { Project } from './project'; + +export const ROOT_REFS_CONFIG_PATH = Path.resolve(REPO_ROOT, 'tsconfig.refs.json'); +export const REF_CONFIG_PATHS = [ROOT_REFS_CONFIG_PATH]; + +const sort = (arr: string[]) => arr.slice().sort((a, b) => a.localeCompare(b)); + +async function analyzeRootRefsConfig(log: ToolingLog) { + const compositeProjects = PROJECTS.filter((p) => p.isCompositeProject() && !p.disableTypeCheck); + const currentRefs = sort(new Project(ROOT_REFS_CONFIG_PATH).getRefdPaths()); + const newRefs = sort( + compositeProjects.map((p) => `./${Path.relative(REPO_ROOT, p.tsConfigPath)}`) + ); + + log.verbose('updating root refs config file'); + log.verbose('existing composite projects', currentRefs); + log.verbose('found the following composite projects', newRefs); + + const removedRefs: string[] = []; + const addedRefs: string[] = []; + + for (let i = 0; i < currentRefs.length || i < newRefs.length; ) { + const currentRef = currentRefs[i]; + const newRef = newRefs[i]; + if (currentRef === newRef) { + // continue with next item + i++; + continue; + } + + // see if this item is lower down in current refs + const newIndex = currentRefs.indexOf(newRef, i + 1); + if (newIndex === -1) { + addedRefs.push(newRef); + currentRefs.splice(i, 0, newRef); + // recheck this index + continue; + } + + // this item was removed from currentRefs + removedRefs.push(currentRef); + currentRefs.splice(i, 1); + // recheck this index + continue; + } + + return { + refs: newRefs, + addedRefs, + removedRefs, + }; +} + +export async function validateRootRefsConfig(log: ToolingLog) { + const { addedRefs, removedRefs } = await analyzeRootRefsConfig(log); + + for (const addedRef of addedRefs) { + log.warning('Missing reference to composite project', addedRef, 'in root refs config file'); + } + for (const removedRef of removedRefs) { + log.warning('Extra reference to non-composite project', removedRef, 'in root refs config file'); + } + + if (addedRefs.length || removedRefs.length) { + throw createFailError( + 'tsconfig.refs.json at the root of the repo is not valid and needs to be updated. Please run `node scripts/build_ts_refs` locally and commit the changes' + ); + } +} + +function generateTsConfig(refs: string[]) { + return dedent` + // This file is automatically updated when you run \`node scripts/build_ts_refs\` and validated to be up-to-date by CI + { + "include": [], + "references": [ +${refs.map((p) => ` { "path": ${JSON.stringify(p)} },`).join('\n')} + ] + } + `; +} + +export async function updateRootRefsConfig(log: ToolingLog) { + const { refs, addedRefs, removedRefs } = await analyzeRootRefsConfig(log); + + if (removedRefs.length === 0 && addedRefs.length === 0) { + log.verbose('root refs config file is up-to-date'); + return; + } + + for (const addedRef of addedRefs) { + log.info('Adding ref to composite project', addedRef, 'to root refs config file'); + } + for (const removedRef of removedRefs) { + log.info('Removing ref to composite project', removedRef, 'to root refs config file'); + } + + await Fs.writeFile(ROOT_REFS_CONFIG_PATH, generateTsConfig(refs) + '\n'); +} diff --git a/src/plugins/input_control_vis/public/input_control_vis_renderer.tsx b/src/plugins/input_control_vis/public/input_control_vis_renderer.tsx index 867fe13aee08f..81ff7a5aa47f1 100644 --- a/src/plugins/input_control_vis/public/input_control_vis_renderer.tsx +++ b/src/plugins/input_control_vis/public/input_control_vis_renderer.tsx @@ -24,8 +24,7 @@ export const getInputControlVisRenderer: ( if (!registeredController) { const { createInputControlVisController } = await import('./vis_controller'); - const Controller = createInputControlVisController(deps, handlers); - registeredController = new Controller(domNode); + registeredController = createInputControlVisController(deps, handlers, domNode); inputControlVisRegistry.set(domNode, registeredController); handlers.onDestroy(() => { diff --git a/src/plugins/input_control_vis/public/vis_controller.tsx b/src/plugins/input_control_vis/public/vis_controller.tsx index 566f9035fe77e..bb09a90bb9dd6 100644 --- a/src/plugins/input_control_vis/public/vis_controller.tsx +++ b/src/plugins/input_control_vis/public/vis_controller.tsx @@ -24,18 +24,17 @@ import { ListControl } from './control/list_control_factory'; import { InputControlVisDependencies } from './plugin'; import { InputControlVisParams } from './types'; -export type InputControlVisControllerType = InstanceType< - ReturnType ->; +export type InputControlVisControllerType = ReturnType; export const createInputControlVisController = ( deps: InputControlVisDependencies, - handlers: IInterpreterRenderHandlers + handlers: IInterpreterRenderHandlers, + el: Element ) => { - return class InputControlVisController { - private I18nContext?: I18nStart['Context']; - private _isLoaded = false; + let I18nContext: I18nStart['Context'] | undefined; + let isLoaded = false; + return new (class InputControlVisController { controls: Array; queryBarUpdateHandler: () => void; filterManager: FilterManager; @@ -43,7 +42,7 @@ export const createInputControlVisController = ( timeFilterSubscription: Subscription; visParams?: InputControlVisParams; - constructor(public el: Element) { + constructor() { this.controls = []; this.queryBarUpdateHandler = this.updateControlsFromKbn.bind(this); @@ -56,21 +55,21 @@ export const createInputControlVisController = ( .getTimeUpdate$() .subscribe(() => { if (this.visParams?.useTimeFilter) { - this._isLoaded = false; + isLoaded = false; } }); } async render(visParams: InputControlVisParams) { - if (!this.I18nContext) { + if (!I18nContext) { const [{ i18n }] = await deps.core.getStartServices(); - this.I18nContext = i18n.Context; + I18nContext = i18n.Context; } - if (!this._isLoaded || !isEqual(visParams, this.visParams)) { + if (!isLoaded || !isEqual(visParams, this.visParams)) { this.visParams = visParams; this.controls = []; this.controls = await this.initControls(visParams); - this._isLoaded = true; + isLoaded = true; } this.drawVis(); } @@ -78,17 +77,17 @@ export const createInputControlVisController = ( destroy() { this.updateSubsciption.unsubscribe(); this.timeFilterSubscription.unsubscribe(); - unmountComponentAtNode(this.el); + unmountComponentAtNode(el); this.controls.forEach((control) => control.destroy()); } drawVis = () => { - if (!this.I18nContext) { + if (!I18nContext) { throw new Error('no i18n context found'); } render( - + - , - this.el + , + el ); }; @@ -235,5 +234,5 @@ export const createInputControlVisController = ( await this.controls[controlIndex].fetch(query); this.drawVis(); }; - }; + })(); }; diff --git a/test/scripts/checks/type_check_plugin_public_api_docs.sh b/test/scripts/checks/type_check_plugin_public_api_docs.sh index 8acacb696c576..c38e241541139 100755 --- a/test/scripts/checks/type_check_plugin_public_api_docs.sh +++ b/test/scripts/checks/type_check_plugin_public_api_docs.sh @@ -4,6 +4,7 @@ source src/dev/ci_setup/setup_env.sh checks-reporter-with-killswitch "Build TS Refs" \ node scripts/build_ts_refs \ + --validate-root-refs-config \ --ignore-type-failures \ --clean \ --no-cache \ diff --git a/tsconfig.refs.json b/tsconfig.refs.json index 1807a7014e389..3d5dc7ffe446d 100644 --- a/tsconfig.refs.json +++ b/tsconfig.refs.json @@ -1,6 +1,8 @@ +// This file is automatically updated when you run `node scripts/build_ts_refs` and validated to be up-to-date by CI { "include": [], "references": [ + { "path": "./examples/preboot_example/tsconfig.json" }, { "path": "./src/core/tsconfig.json" }, { "path": "./src/plugins/advanced_settings/tsconfig.json" }, { "path": "./src/plugins/apm_oss/tsconfig.json" }, @@ -13,8 +15,19 @@ { "path": "./src/plugins/discover/tsconfig.json" }, { "path": "./src/plugins/embeddable/tsconfig.json" }, { "path": "./src/plugins/es_ui_shared/tsconfig.json" }, + { "path": "./src/plugins/expression_error/tsconfig.json" }, + { "path": "./src/plugins/expression_image/tsconfig.json" }, + { "path": "./src/plugins/expression_metric/tsconfig.json" }, + { "path": "./src/plugins/expression_repeat_image/tsconfig.json" }, + { "path": "./src/plugins/expression_reveal_image/tsconfig.json" }, + { "path": "./src/plugins/expression_shape/tsconfig.json" }, { "path": "./src/plugins/expressions/tsconfig.json" }, + { "path": "./src/plugins/field_formats/tsconfig.json" }, { "path": "./src/plugins/home/tsconfig.json" }, + { "path": "./src/plugins/index_pattern_editor/tsconfig.json" }, + { "path": "./src/plugins/index_pattern_field_editor/tsconfig.json" }, + { "path": "./src/plugins/index_pattern_management/tsconfig.json" }, + { "path": "./src/plugins/input_control_vis/tsconfig.json" }, { "path": "./src/plugins/inspector/tsconfig.json" }, { "path": "./src/plugins/interactive_setup/tsconfig.json" }, { "path": "./src/plugins/kibana_legacy/tsconfig.json" }, @@ -24,6 +37,7 @@ { "path": "./src/plugins/kibana_utils/tsconfig.json" }, { "path": "./src/plugins/legacy_export/tsconfig.json" }, { "path": "./src/plugins/management/tsconfig.json" }, + { "path": "./src/plugins/maps_ems/tsconfig.json" }, { "path": "./src/plugins/maps_legacy/tsconfig.json" }, { "path": "./src/plugins/navigation/tsconfig.json" }, { "path": "./src/plugins/newsfeed/tsconfig.json" }, @@ -32,6 +46,7 @@ { "path": "./src/plugins/saved_objects_management/tsconfig.json" }, { "path": "./src/plugins/saved_objects_tagging_oss/tsconfig.json" }, { "path": "./src/plugins/saved_objects/tsconfig.json" }, + { "path": "./src/plugins/screenshot_mode/tsconfig.json" }, { "path": "./src/plugins/security_oss/tsconfig.json" }, { "path": "./src/plugins/share/tsconfig.json" }, { "path": "./src/plugins/spaces_oss/tsconfig.json" }, @@ -46,28 +61,33 @@ { "path": "./src/plugins/vis_default_editor/tsconfig.json" }, { "path": "./src/plugins/vis_type_markdown/tsconfig.json" }, { "path": "./src/plugins/vis_type_metric/tsconfig.json" }, + { "path": "./src/plugins/vis_type_pie/tsconfig.json" }, { "path": "./src/plugins/vis_type_table/tsconfig.json" }, { "path": "./src/plugins/vis_type_tagcloud/tsconfig.json" }, { "path": "./src/plugins/vis_type_timelion/tsconfig.json" }, { "path": "./src/plugins/vis_type_timeseries/tsconfig.json" }, - { "path": "./src/plugins/vis_type_vislib/tsconfig.json" }, { "path": "./src/plugins/vis_type_vega/tsconfig.json" }, + { "path": "./src/plugins/vis_type_vislib/tsconfig.json" }, { "path": "./src/plugins/vis_type_xy/tsconfig.json" }, - { "path": "./src/plugins/vis_type_pie/tsconfig.json" }, { "path": "./src/plugins/visualizations/tsconfig.json" }, { "path": "./src/plugins/visualize/tsconfig.json" }, - { "path": "./src/plugins/index_pattern_management/tsconfig.json" }, - { "path": "./src/plugins/index_pattern_editor/tsconfig.json" }, + { "path": "./test/plugin_functional/plugins/core_app_status/tsconfig.json" }, + { "path": "./test/plugin_functional/plugins/core_provider_plugin/tsconfig.json" }, + { "path": "./test/server_integration/__fixtures__/plugins/status_plugin_a/tsconfig.json" }, + { "path": "./test/server_integration/__fixtures__/plugins/status_plugin_b/tsconfig.json" }, { "path": "./test/tsconfig.json" }, { "path": "./x-pack/plugins/actions/tsconfig.json" }, { "path": "./x-pack/plugins/alerting/tsconfig.json" }, { "path": "./x-pack/plugins/apm/tsconfig.json" }, + { "path": "./x-pack/plugins/banners/tsconfig.json" }, { "path": "./x-pack/plugins/canvas/tsconfig.json" }, { "path": "./x-pack/plugins/cases/tsconfig.json" }, { "path": "./x-pack/plugins/cloud/tsconfig.json" }, + { "path": "./x-pack/plugins/cross_cluster_replication/tsconfig.json" }, { "path": "./x-pack/plugins/dashboard_enhanced/tsconfig.json" }, - { "path": "./x-pack/plugins/data_enhanced/tsconfig.json" }, { "path": "./x-pack/plugins/dashboard_mode/tsconfig.json" }, + { "path": "./x-pack/plugins/data_enhanced/tsconfig.json" }, + { "path": "./x-pack/plugins/data_visualizer/tsconfig.json" }, { "path": "./x-pack/plugins/discover_enhanced/tsconfig.json" }, { "path": "./x-pack/plugins/drilldowns/url_drilldown/tsconfig.json" }, { "path": "./x-pack/plugins/embeddable_enhanced/tsconfig.json" }, @@ -82,9 +102,12 @@ { "path": "./x-pack/plugins/global_search/tsconfig.json" }, { "path": "./x-pack/plugins/graph/tsconfig.json" }, { "path": "./x-pack/plugins/grokdebugger/tsconfig.json" }, + { "path": "./x-pack/plugins/index_lifecycle_management/tsconfig.json" }, + { "path": "./x-pack/plugins/index_management/tsconfig.json" }, { "path": "./x-pack/plugins/infra/tsconfig.json" }, { "path": "./x-pack/plugins/ingest_pipelines/tsconfig.json" }, { "path": "./x-pack/plugins/lens/tsconfig.json" }, + { "path": "./x-pack/plugins/license_api_guard/tsconfig.json" }, { "path": "./x-pack/plugins/license_management/tsconfig.json" }, { "path": "./x-pack/plugins/licensing/tsconfig.json" }, { "path": "./x-pack/plugins/lists/tsconfig.json" }, @@ -96,11 +119,15 @@ { "path": "./x-pack/plugins/observability/tsconfig.json" }, { "path": "./x-pack/plugins/osquery/tsconfig.json" }, { "path": "./x-pack/plugins/painless_lab/tsconfig.json" }, + { "path": "./x-pack/plugins/remote_clusters/tsconfig.json" }, { "path": "./x-pack/plugins/reporting/tsconfig.json" }, + { "path": "./x-pack/plugins/rollup/tsconfig.json" }, + { "path": "./x-pack/plugins/rule_registry/tsconfig.json" }, + { "path": "./x-pack/plugins/runtime_fields/tsconfig.json" }, { "path": "./x-pack/plugins/saved_objects_tagging/tsconfig.json" }, { "path": "./x-pack/plugins/searchprofiler/tsconfig.json" }, - { "path": "./x-pack/plugins/security/tsconfig.json" }, { "path": "./x-pack/plugins/security_solution/tsconfig.json" }, + { "path": "./x-pack/plugins/security/tsconfig.json" }, { "path": "./x-pack/plugins/snapshot_restore/tsconfig.json" }, { "path": "./x-pack/plugins/spaces/tsconfig.json" }, { "path": "./x-pack/plugins/stack_alerts/tsconfig.json" }, @@ -112,14 +139,8 @@ { "path": "./x-pack/plugins/triggers_actions_ui/tsconfig.json" }, { "path": "./x-pack/plugins/ui_actions_enhanced/tsconfig.json" }, { "path": "./x-pack/plugins/upgrade_assistant/tsconfig.json" }, - { "path": "./x-pack/plugins/runtime_fields/tsconfig.json" }, - { "path": "./x-pack/plugins/index_management/tsconfig.json" }, - { "path": "./x-pack/plugins/watcher/tsconfig.json" }, - { "path": "./x-pack/plugins/rollup/tsconfig.json" }, - { "path": "./x-pack/plugins/remote_clusters/tsconfig.json" }, - { "path": "./x-pack/plugins/cross_cluster_replication/tsconfig.json" }, - { "path": "./x-pack/plugins/index_lifecycle_management/tsconfig.json" }, { "path": "./x-pack/plugins/uptime/tsconfig.json" }, + { "path": "./x-pack/plugins/watcher/tsconfig.json" }, { "path": "./x-pack/plugins/xpack_legacy/tsconfig.json" }, { "path": "./x-pack/test/tsconfig.json" }, ]