From a7e07558f806b46f2aeb47a4f48d945728aaea52 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 11 Feb 2021 02:38:33 -0500 Subject: [PATCH] [ts/build_ts_refs] add support for --clean flag (#91060) (#91078) Co-authored-by: spalger Co-authored-by: Spencer Co-authored-by: spalger --- scripts/build_ts_refs.js | 2 +- src/dev/typescript/build_refs.ts | 35 ------------ src/dev/typescript/build_ts_refs.ts | 24 ++++++++ src/dev/typescript/build_ts_refs_cli.ts | 37 ++++++++++++ src/dev/typescript/concurrent_map.ts | 28 ++++++++++ src/dev/typescript/index.ts | 1 + src/dev/typescript/project.ts | 15 +---- src/dev/typescript/run_type_check_cli.ts | 4 +- src/dev/typescript/ts_configfile.ts | 71 ++++++++++++++++++++++++ 9 files changed, 166 insertions(+), 51 deletions(-) delete mode 100644 src/dev/typescript/build_refs.ts create mode 100644 src/dev/typescript/build_ts_refs.ts create mode 100644 src/dev/typescript/build_ts_refs_cli.ts create mode 100644 src/dev/typescript/concurrent_map.ts create mode 100644 src/dev/typescript/ts_configfile.ts diff --git a/scripts/build_ts_refs.js b/scripts/build_ts_refs.js index 3222e0e90797bb..a4ee6ec491ef15 100644 --- a/scripts/build_ts_refs.js +++ b/scripts/build_ts_refs.js @@ -7,4 +7,4 @@ */ require('../src/setup_node_env'); -require('../src/dev/typescript/build_refs').runBuildRefs(); +require('../src/dev/typescript').runBuildRefsCli(); diff --git a/src/dev/typescript/build_refs.ts b/src/dev/typescript/build_refs.ts deleted file mode 100644 index 77d6eb2abc6126..00000000000000 --- a/src/dev/typescript/build_refs.ts +++ /dev/null @@ -1,35 +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 execa from 'execa'; -import { run, ToolingLog } from '@kbn/dev-utils'; - -export async function buildAllRefs(log: ToolingLog) { - await buildRefs(log, 'tsconfig.refs.json'); -} - -async function buildRefs(log: ToolingLog, projectPath: string) { - try { - log.debug(`Building TypeScript projects refs for ${projectPath}...`); - await execa(require.resolve('typescript/bin/tsc'), ['-b', projectPath, '--pretty']); - } catch (e) { - log.error(e); - process.exit(1); - } -} - -export async function runBuildRefs() { - run( - async ({ log }) => { - await buildAllRefs(log); - }, - { - description: 'Build TypeScript projects', - } - ); -} diff --git a/src/dev/typescript/build_ts_refs.ts b/src/dev/typescript/build_ts_refs.ts new file mode 100644 index 00000000000000..2e25827996e453 --- /dev/null +++ b/src/dev/typescript/build_ts_refs.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 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')]; + +export async function buildAllTsRefs(log: ToolingLog) { + for (const path of REF_CONFIG_PATHS) { + const relative = Path.relative(REPO_ROOT, path); + log.debug(`Building TypeScript projects refs for ${relative}...`); + await execa(require.resolve('typescript/bin/tsc'), ['-b', relative, '--pretty'], { + cwd: REPO_ROOT, + }); + } +} diff --git a/src/dev/typescript/build_ts_refs_cli.ts b/src/dev/typescript/build_ts_refs_cli.ts new file mode 100644 index 00000000000000..1f7bf18b5012d9 --- /dev/null +++ b/src/dev/typescript/build_ts_refs_cli.ts @@ -0,0 +1,37 @@ +/* + * 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 { run } from '@kbn/dev-utils'; +import del from 'del'; + +import { buildAllTsRefs, REF_CONFIG_PATHS } from './build_ts_refs'; +import { getOutputsDeep } from './ts_configfile'; +import { concurrentMap } from './concurrent_map'; + +export async function runBuildRefsCli() { + run( + async ({ log, flags }) => { + if (flags.clean) { + const outDirs = getOutputsDeep(REF_CONFIG_PATHS); + log.info('deleting', outDirs.length, 'ts output directories'); + await concurrentMap(100, outDirs, (outDir) => del(outDir)); + } + + await buildAllTsRefs(log); + }, + { + description: 'Build TypeScript projects', + flags: { + boolean: ['clean'], + }, + log: { + defaultLevel: 'debug', + }, + } + ); +} diff --git a/src/dev/typescript/concurrent_map.ts b/src/dev/typescript/concurrent_map.ts new file mode 100644 index 00000000000000..793630ab85a553 --- /dev/null +++ b/src/dev/typescript/concurrent_map.ts @@ -0,0 +1,28 @@ +/* + * 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 { mergeMap, toArray, map } from 'rxjs/operators'; +import { lastValueFrom } from '@kbn/std'; + +export async function concurrentMap( + concurrency: number, + arr: T[], + fn: (item: T, i: number) => Promise +): Promise { + return await lastValueFrom( + Rx.from(arr).pipe( + // execute items in parallel based on concurrency + mergeMap(async (item, index) => ({ index, result: await fn(item, index) }), concurrency), + // collect the results into an array + toArray(), + // sort items back into order and return array of just results + map((list) => list.sort((a, b) => a.index - b.index).map(({ result }) => result)) + ) + ); +} diff --git a/src/dev/typescript/index.ts b/src/dev/typescript/index.ts index 934c032152fafd..34ecd76a994db4 100644 --- a/src/dev/typescript/index.ts +++ b/src/dev/typescript/index.ts @@ -11,3 +11,4 @@ export { filterProjectsByFlag } from './projects'; export { getTsProjectForAbsolutePath } from './get_ts_project_for_absolute_path'; export { execInProjects } from './exec_in_projects'; export { runTypeCheckCli } from './run_type_check_cli'; +export * from './build_ts_refs_cli'; diff --git a/src/dev/typescript/project.ts b/src/dev/typescript/project.ts index 4eb8036d9c9024..8d92284e496379 100644 --- a/src/dev/typescript/project.ts +++ b/src/dev/typescript/project.ts @@ -6,14 +6,13 @@ * Side Public License, v 1. */ -import { readFileSync } from 'fs'; import { basename, dirname, relative, resolve } from 'path'; import { IMinimatch, Minimatch } from 'minimatch'; -import { parseConfigFileTextToJson } from 'typescript'; - import { REPO_ROOT } from '@kbn/utils'; +import { parseTsConfig } from './ts_configfile'; + function makeMatchers(directory: string, patterns: string[]) { return patterns.map( (pattern) => @@ -23,16 +22,6 @@ function makeMatchers(directory: string, patterns: string[]) { ); } -function parseTsConfig(path: string) { - const { error, config } = parseConfigFileTextToJson(path, readFileSync(path, 'utf8')); - - if (error) { - throw error; - } - - return config; -} - function testMatchers(matchers: IMinimatch[], path: string) { return matchers.some((matcher) => matcher.match(path)); } diff --git a/src/dev/typescript/run_type_check_cli.ts b/src/dev/typescript/run_type_check_cli.ts index 50f725891753bc..f95c230f44b9e4 100644 --- a/src/dev/typescript/run_type_check_cli.ts +++ b/src/dev/typescript/run_type_check_cli.ts @@ -13,7 +13,7 @@ import getopts from 'getopts'; import { execInProjects } from './exec_in_projects'; import { filterProjectsByFlag } from './projects'; -import { buildAllRefs } from './build_refs'; +import { buildAllTsRefs } from './build_ts_refs'; export async function runTypeCheckCli() { const extraFlags: string[] = []; @@ -69,7 +69,7 @@ export async function runTypeCheckCli() { process.exit(); } - await buildAllRefs(log); + await buildAllTsRefs(log); const tscArgs = [ // composite project cannot be used with --noEmit diff --git a/src/dev/typescript/ts_configfile.ts b/src/dev/typescript/ts_configfile.ts new file mode 100644 index 00000000000000..7998edcf80bcf1 --- /dev/null +++ b/src/dev/typescript/ts_configfile.ts @@ -0,0 +1,71 @@ +/* + * 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 Path from 'path'; + +import { parseConfigFileTextToJson } from 'typescript'; + +// yes, this is just `any`, but I'm hoping that TypeScript will give us some help here eventually +type TsConfigFile = ReturnType['config']; + +export function parseTsConfig(tsConfigPath: string): TsConfigFile { + const { error, config } = parseConfigFileTextToJson( + tsConfigPath, + Fs.readFileSync(tsConfigPath, 'utf8') + ); + + if (error) { + throw error; + } + + return config; +} + +export function getOutputsDeep(tsConfigPaths: string[]) { + const tsConfigs = new Map(); + + const read = (path: string) => { + const cached = tsConfigs.get(path); + if (cached) { + return cached; + } + + const config = parseTsConfig(path); + tsConfigs.set(path, config); + return config; + }; + + const outputDirs: string[] = []; + const seen = new Set(); + + const traverse = (path: string) => { + const config = read(path); + if (seen.has(config)) { + return; + } + seen.add(config); + + const dirname = Path.dirname(path); + const relativeOutDir: string | undefined = config.compilerOptions?.outDir; + if (relativeOutDir) { + outputDirs.push(Path.resolve(dirname, relativeOutDir)); + } + + const refs: undefined | Array<{ path: string }> = config.references; + for (const ref of refs ?? []) { + traverse(Path.resolve(dirname, ref.path)); + } + }; + + for (const path of tsConfigPaths) { + traverse(path); + } + + return outputDirs; +}