diff --git a/scripts/tasks/src/api-extractor.ts b/scripts/tasks/src/api-extractor.ts index 336ad6667301c4..96c8cc8b6cf38f 100644 --- a/scripts/tasks/src/api-extractor.ts +++ b/scripts/tasks/src/api-extractor.ts @@ -57,7 +57,7 @@ export function apiExtractor(): TaskFunction { }; const args: ReturnType & Partial = getJustArgv(); - const { isUsingTsSolutionConfigs, packageJson, tsConfig } = getTsPathAliasesConfig(); + const { isUsingTsSolutionConfigs, packageJson, tsConfigs } = getTsPathAliasesConfig(); if (configsToExecute.length === 0) { return noop; @@ -102,14 +102,14 @@ export function apiExtractor(): TaskFunction { } function onConfigLoaded(config: Parameters>[0]) { - if (!(isUsingTsSolutionConfigs && tsConfig)) { + if (!(isUsingTsSolutionConfigs && tsConfigs.lib)) { return; } logger.info(`api-extractor: package is using TS path aliases. Overriding TS compiler settings.`); const compilerConfig = getTsPathAliasesApiExtractorConfig({ - tsConfig, + tsConfig: tsConfigs.lib, packageJson, pathAliasesTsConfigPath: isLocalBuild ? path.join(workspaceRoot, 'tsconfig.base.json') : undefined, definitionsRootPath: 'dist/out-tsc/types', diff --git a/scripts/tasks/src/copy.ts b/scripts/tasks/src/copy.ts index f750762477ce24..61e911f8333d9e 100644 --- a/scripts/tasks/src/copy.ts +++ b/scripts/tasks/src/copy.ts @@ -1,7 +1,9 @@ -import * as fs from 'fs-extra'; import * as path from 'path'; -import { series, resolveCwd, copyTask, copyInstructionsTask, logger, TaskFunction } from 'just-scripts'; -import { getProjectMetadata, findGitRoot } from '@fluentui/scripts-monorepo'; + +import { findGitRoot, getProjectMetadata } from '@fluentui/scripts-monorepo'; +import * as fs from 'fs-extra'; +import { TaskFunction, copyInstructionsTask, copyTask, logger, resolveCwd, series } from 'just-scripts'; + import { getTsPathAliasesConfig } from './utils'; export function expandSourcePath(pattern: string): string | null { @@ -38,10 +40,11 @@ export function expandSourcePath(pattern: string): string | null { * Used solely for packages that use TS solution config files with TS path aliases */ export function copyCompiled() { - const { isUsingTsSolutionConfigs, packageJson, tsConfig } = getTsPathAliasesConfig(); + const { isUsingTsSolutionConfigs, packageJson, tsConfigs } = getTsPathAliasesConfig(); const root = findGitRoot(); const packageDir = process.cwd(); + const tsConfig = tsConfigs.lib; if (!(isUsingTsSolutionConfigs && tsConfig)) { logger.warn(`copy-compiled: works only with packages that use TS solution config files. Skipping...`); diff --git a/scripts/tasks/src/generate-api.ts b/scripts/tasks/src/generate-api.ts index 829339d5cd4c89..6a598757c8eb25 100644 --- a/scripts/tasks/src/generate-api.ts +++ b/scripts/tasks/src/generate-api.ts @@ -1,17 +1,24 @@ -import { exec } from 'child_process'; -import { promisify } from 'util'; +import { execSync } from 'child_process'; import { series } from 'just-scripts'; import { apiExtractor } from './api-extractor'; - -const execAsync = promisify(exec); +import { getTsPathAliasesConfigUsedOnlyForDx } from './utils'; export function generateApi() { return series(generateTypeDeclarations, apiExtractor); } function generateTypeDeclarations() { - const cmd = 'tsc -p ./tsconfig.lib.json --emitDeclarationOnly'; - return execAsync(cmd); + const { isUsingPathAliasesForDx, tsConfigFileForCompilation } = getTsPathAliasesConfigUsedOnlyForDx(); + const cmd = [ + 'tsc', + `-p ./${tsConfigFileForCompilation}`, + '--emitDeclarationOnly', + isUsingPathAliasesForDx ? '--baseUrl .' : null, + ] + .filter(Boolean) + .join(' '); + + return execSync(cmd, { stdio: 'inherit' }); } diff --git a/scripts/tasks/src/presets.ts b/scripts/tasks/src/presets.ts index 47a4bd4b7dede6..9ddfeedbbaa4fc 100644 --- a/scripts/tasks/src/presets.ts +++ b/scripts/tasks/src/presets.ts @@ -20,6 +20,7 @@ import { hasSass, sass } from './sass'; import { buildStorybookTask, startStorybookTask } from './storybook'; import { swc } from './swc'; import { ts } from './ts'; +import { typeCheck } from './type-check'; import { webpack, webpackDevServer } from './webpack'; /** Do only the bare minimum setup of options and resolve paths */ @@ -72,6 +73,7 @@ export function preset() { task('storybook:build', buildStorybookTask()); task('babel:postprocess', babel); task('generate-api', generateApi); + task('type-check', typeCheck); task('ts:compile', () => { const moduleFlag = args.module; diff --git a/scripts/tasks/src/ts.ts b/scripts/tasks/src/ts.ts index 63e432839fffbc..89388cd5228c9c 100644 --- a/scripts/tasks/src/ts.ts +++ b/scripts/tasks/src/ts.ts @@ -31,17 +31,19 @@ function prepareTsTaskConfig(options: TscTaskOptions) { options.baseUrl = '.'; options.rootDir = './src'; options.project = tsConfigFileForCompilation; + + return options; } - const { isUsingTsSolutionConfigs, tsConfigFile, tsConfig } = getTsPathAliasesConfig(); + const { isUsingTsSolutionConfigs, tsConfigFileNames, tsConfigs } = getTsPathAliasesConfig(); - if (isUsingTsSolutionConfigs && tsConfig) { + if (isUsingTsSolutionConfigs && tsConfigs.lib) { logger.info(`📣 TSC: package is using TS path aliases. Overriding tsconfig settings.`); - const tsConfigOutDir = tsConfig.compilerOptions.outDir as string; + const tsConfigOutDir = tsConfigs.lib.compilerOptions.outDir as string; options.outDir = `${tsConfigOutDir}/${options.outDir}`; - options.project = tsConfigFile; + options.project = tsConfigFileNames.lib; } return options; diff --git a/scripts/tasks/src/type-check.ts b/scripts/tasks/src/type-check.ts new file mode 100644 index 00000000000000..72f73f2fc62fba --- /dev/null +++ b/scripts/tasks/src/type-check.ts @@ -0,0 +1,43 @@ +import * as fs from 'fs'; + +import { logger } from 'just-scripts'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { exec } from 'just-scripts-utils'; + +import { getTsPathAliasesConfig } from './utils'; + +export function typeCheck() { + const { isUsingTsSolutionConfigs, tsConfigFileContents, tsConfigs, tsConfigFilePaths } = getTsPathAliasesConfig(); + + if (!isUsingTsSolutionConfigs) { + logger.info('project is not using TS solution config. skipping...'); + return; + } + + const content = tsConfigFileContents.root; + const config = tsConfigs.root; + const configPath = tsConfigFilePaths.root; + + if (!(content && config)) { + return; + } + + // turn off path aliases. + // @ts-expect-error - bad just-scripts ts types + config.compilerOptions.paths = {}; + fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8'); + + const cmd = 'tsc'; + const args = ['-b', '--pretty', configPath]; + const program = `${cmd} ${args.join(' ')}`; + + return exec(program) + .catch(err => { + console.error(err.stdout); + process.exit(1); + }) + .finally(() => { + // restore original tsconfig.json + fs.writeFileSync(configPath, content, 'utf-8'); + }); +} diff --git a/scripts/tasks/src/utils.ts b/scripts/tasks/src/utils.ts index e1d14628cb5da9..1df51f3a0e555b 100644 --- a/scripts/tasks/src/utils.ts +++ b/scripts/tasks/src/utils.ts @@ -1,22 +1,64 @@ +import { execSync } from 'child_process'; import * as fs from 'fs'; import * as path from 'path'; import { stripJsonComments } from '@nrwl/devkit'; -import * as jju from 'jju'; import type { TscTaskOptions } from 'just-scripts'; +/** + * + * get full TS configuration for particular tsconfig.json. + * uses tsc --showConfig under the hood + */ +export function getFullTsConfig(options: { configFileName: string; cwd: string }): TsConfig | null { + const { configFileName, cwd } = options; + const configPath = path.join(cwd, configFileName); + + if (!fs.existsSync(configPath)) { + console.warn(`${configPath} doesn't exist`); + return null; + } + + const output = execSync(`tsc -p ${configFileName} --showConfig`, { cwd, encoding: 'utf-8' }); + const json = JSON.parse(output); + + return json; +} + export function getTsPathAliasesConfig() { const cwd = process.cwd(); - const tsConfigFile = 'tsconfig.lib.json'; - const tsConfigPath = path.join(cwd, './tsconfig.lib.json'); - const tsConfig: TsConfig | null = fs.existsSync(tsConfigPath) - ? jju.parse(fs.readFileSync(tsConfigPath, 'utf-8')) - : null; + const tsConfigFileNames = { + root: 'tsconfig.json', + lib: 'tsconfig.lib.json', + }; + const tsConfigFilePaths = { + root: path.join(cwd, tsConfigFileNames.root), + lib: path.join(cwd, tsConfigFileNames.lib), + }; + const tsConfigFileContents = { + root: fs.existsSync(tsConfigFilePaths.root) ? fs.readFileSync(tsConfigFilePaths.root, 'utf-8') : null, + lib: fs.existsSync(tsConfigFilePaths.lib) ? fs.readFileSync(tsConfigFilePaths.lib, 'utf-8') : null, + }; + const tsConfigs = { + root: tsConfigFileContents.root ? (JSON.parse(tsConfigFileContents.root) as TsConfig) : null, + lib: tsConfigFileContents.lib ? (JSON.parse(tsConfigFileContents.lib) as TsConfig) : null, + }; const packageJson: PackageJson = JSON.parse(fs.readFileSync(path.join(cwd, './package.json'), 'utf-8')); - const isUsingTsSolutionConfigs = Boolean(tsConfig); + const isUsingTsSolutionConfigs = + tsConfigs.root && + tsConfigs.root.references && + tsConfigs.root.references.length > 0 && + Boolean(tsConfigFileContents.lib); - return { tsConfig, isUsingTsSolutionConfigs, tsConfigFile, tsConfigPath, packageJson }; + return { + tsConfigFileNames, + tsConfigFilePaths, + tsConfigFileContents, + tsConfigs, + packageJson, + isUsingTsSolutionConfigs, + }; } export function getTsPathAliasesConfigUsedOnlyForDx() { @@ -24,6 +66,11 @@ export function getTsPathAliasesConfigUsedOnlyForDx() { const tsConfigBaseFilesForDx = ['tsconfig.base.v8.json', 'tsconfig.base.all.json']; const cwd = process.cwd(); const tsConfigPath = path.join(cwd, `./tsconfig.json`); + + if (!fs.existsSync(tsConfigPath)) { + throw new Error(`${tsConfigPath} doesn't exist`); + } + const tsConfig = JSON.parse(stripJsonComments(fs.readFileSync(tsConfigPath, 'utf-8'))); const isUsingPathAliasesForDx = tsConfig.extends && tsConfigBaseFilesForDx.some(relativeFilePath => tsConfig.extends.endsWith(relativeFilePath)); @@ -31,7 +78,7 @@ export function getTsPathAliasesConfigUsedOnlyForDx() { const tsConfigFileForCompilation = tsConfigFilesWithAliases.find(fileName => fs.existsSync(path.join(cwd, fileName))); if (!tsConfigFileForCompilation) { - throw new Error(`no tsconfig from ${tsConfigFilesWithAliases} found!`); + throw new Error(`no tsconfig from one of [${tsConfigFilesWithAliases}] found!`); } return { isUsingPathAliasesForDx, tsConfigFileForCompilation };