From c59ff3cac6ab18dbf0eda3bbeab33df9376da3fb Mon Sep 17 00:00:00 2001 From: Martin Hochel Date: Wed, 14 Feb 2024 19:33:33 +0100 Subject: [PATCH] feat(tasks): implement new type-check that use nx generation approach + programatic TS apis --- scripts/tasks/src/presets.ts | 2 + scripts/tasks/src/type-check-v2.ts | 111 +++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 scripts/tasks/src/type-check-v2.ts diff --git a/scripts/tasks/src/presets.ts b/scripts/tasks/src/presets.ts index 681533cb41a50d..18016eacc80473 100644 --- a/scripts/tasks/src/presets.ts +++ b/scripts/tasks/src/presets.ts @@ -23,6 +23,7 @@ import { ts } from './ts'; import { typeCheck } from './type-check'; import { verifyPackaging } from './verify-packaging'; import { webpack, webpackDevServer } from './webpack'; +import { typeCheckV2 } from './type-check-v2'; /** Do only the bare minimum setup of options and resolve paths */ function basicPreset() { @@ -75,6 +76,7 @@ export function preset() { task('babel:postprocess', babel); task('generate-api', generateApi); task('type-check', typeCheck); + task('type-check-v2', typeCheckV2); task('verify-packaging', () => verifyPackaging(args)); task('ts:compile', () => { diff --git a/scripts/tasks/src/type-check-v2.ts b/scripts/tasks/src/type-check-v2.ts new file mode 100644 index 00000000000000..b3b01aeb322e5a --- /dev/null +++ b/scripts/tasks/src/type-check-v2.ts @@ -0,0 +1,111 @@ +import * as fs from 'fs'; +import * as path from 'node:path'; + +import { workspaceRoot, writeJsonFile } from '@nx/devkit'; +import { readTsConfig } from '@nx/js/src/utils/typescript/ts-config'; +import { logger } from 'just-scripts'; +import * as ts from 'typescript'; + +import { getTsPathAliasesConfig } from './utils'; + +// eslint-disable-next-line @typescript-eslint/no-shadow +function createTmpTsConfig(tsconfigPath: string, workspaceRoot: string, projectRoot: string) { + const tmpTsConfigPath = path.join(workspaceRoot, 'tmp', projectRoot, 'tsconfig.generated.json'); + const parsedTSConfig = readTsConfigWithRemappedPaths(path.join(workspaceRoot, tsconfigPath), tmpTsConfigPath); + + process.on('exit', () => cleanupTmpTsConfigFile(tmpTsConfigPath)); + + writeJsonFile(tmpTsConfigPath, parsedTSConfig); + + return { tmpTsConfigPath }; +} + +function readTsConfigWithRemappedPaths(tsConfigPath: string, generatedTsConfigPath: string) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const generatedTsConfig: Record = { compilerOptions: {} }; + const originalConfig = readTsConfig(tsConfigPath); + + generatedTsConfig.extends = path.relative(path.dirname(generatedTsConfigPath), tsConfigPath); + generatedTsConfig.compilerOptions.paths = updatePaths(originalConfig.options.paths ?? {}); + generatedTsConfig.include = originalConfig.raw.include; + generatedTsConfig.files = originalConfig.raw.files; + generatedTsConfig.references = originalConfig.raw.references.map((ref: { path: string }) => { + const oldPath = ref.path; + const newPath = path.relative(path.dirname(generatedTsConfigPath), oldPath); + return { path: newPath }; + }); + + // if (process.env.NX_VERBOSE_LOGGING_PATH_MAPPINGS === 'true') { + // output.log({ + // title: 'TypeScript path mappings have been rewritten.', + // }); + // console.log(JSON.stringify(generatedTsConfig.compilerOptions.paths, null, 2)); + // } + return generatedTsConfig; +} + +function updatePaths(paths: Record) { + // turn off path aliases for now + return {}; +} + +function cleanupTmpTsConfigFile(tmpTsConfigPath: string) { + try { + if (tmpTsConfigPath) { + fs.unlinkSync(tmpTsConfigPath); + } + // eslint-disable-next-line no-empty + } catch (err) {} +} + +export function typeCheckV2() { + const { isUsingTsSolutionConfigs, tsConfigFilePaths, projectRoot } = getTsPathAliasesConfig(); + + if (!isUsingTsSolutionConfigs) { + logger.info('project is not using TS solution config. skipping...'); + return; + } + + const tmpTsConfig = createTmpTsConfig( + path.relative(workspaceRoot, tsConfigFilePaths.root), + workspaceRoot, + path.relative(workspaceRoot, projectRoot), + ); + + tscBuild(tmpTsConfig.tmpTsConfigPath); +} + +const formatHost: ts.FormatDiagnosticsHost = { + // eslint-disable-next-line @typescript-eslint/no-shadow + getCanonicalFileName: path => path, + getCurrentDirectory: ts.sys.getCurrentDirectory, + getNewLine: () => ts.sys.newLine, +}; + +function tscBuild(tsConfigPath: string) { + // const tsSolutionConfigPath = ts.findConfigFile(rootPath, ts.sys.fileExists, 'tsconfig.json'); + + if (!tsConfigPath) { + throw new Error("Could not find a valid 'tsconfig.json'."); + } + + // const createProgram = ts.createSemanticDiagnosticsBuilderProgram; + + // const host = ts.createSolutionBuilderHost(undefined, createProgram); + const host = ts.createSolutionBuilderHost(ts.sys, /*createProgram*/ undefined, diagnostic => { + const formattedDiagnostic = ts.formatDiagnosticsWithColorAndContext([diagnostic], formatHost); + + console.info(formattedDiagnostic); + }); + + const solutionBuilder = ts.createSolutionBuilder(host, [tsConfigPath], {}); + + const buildResult = solutionBuilder.build(); + + if (buildResult > 0) { + console.error('Build failed'); + process.exit(1); + } + + console.log('Build succeeded'); +}