diff --git a/scripts/tasks/src/api-extractor.ts b/scripts/tasks/src/api-extractor.ts index 7902aae3ad81ca..9114ab5b523a28 100644 --- a/scripts/tasks/src/api-extractor.ts +++ b/scripts/tasks/src/api-extractor.ts @@ -1,6 +1,7 @@ import * as path from 'path'; import type { ExtractorMessageCategory, ExtractorResult } from '@microsoft/api-extractor'; +import { workspaceRoot } from '@nrwl/devkit'; import chalk from 'chalk'; import * as glob from 'glob'; import { ApiExtractorOptions, TaskFunction, apiExtractorVerifyTask, logger, series, task } from 'just-scripts'; @@ -56,12 +57,18 @@ export function apiExtractor(): TaskFunction { let configDebug: Parameters>[0] | null = null; const args: ReturnType & Partial = getJustArgv(); - const { isUsingTsSolutionConfigs, packageJson, tsConfig, tsConfigPath } = getTsPathAliasesConfig(); + const { isUsingTsSolutionConfigs, packageJson, tsConfig } = getTsPathAliasesConfig(); if (configsToExecute.length === 0) { return noop; } + /** + * overrides api-extractor default `true` to be `false` on local dev machine + * Triggers if path aliases will be used or yarn workspaces (that needs to be build based on package dependency tree) + */ + const isLocalBuild = args.local ?? !process.env.TF_BUILD; + const tasks = configsToExecute.map(([configPath, configName]) => { const taskName = `api-extractor:${configName}`; @@ -73,7 +80,7 @@ export function apiExtractor(): TaskFunction { showDiagnostics: args.diagnostics, typescriptCompilerFolder: args['typescript-compiler-folder'], configJsonFilePath: args.config ?? configPath, - localBuild: args.local ?? !process.env.TF_BUILD, + localBuild: isLocalBuild, onConfigLoaded, messageCallback, onResult, @@ -103,8 +110,8 @@ export function apiExtractor(): TaskFunction { const compilerConfig = getTsPathAliasesApiExtractorConfig({ tsConfig, - tsConfigPath, packageJson, + pathAliasesTsConfigPath: isLocalBuild ? path.join(workspaceRoot, 'tsconfig.base.json') : undefined, definitionsRootPath: 'dist/out-tsc/types', }); diff --git a/scripts/tasks/src/utils.spec.ts b/scripts/tasks/src/utils.spec.ts index b6c36d6dfecae7..a30d6da02a5894 100644 --- a/scripts/tasks/src/utils.spec.ts +++ b/scripts/tasks/src/utils.spec.ts @@ -1,3 +1,7 @@ +import * as path from 'path'; + +import { workspaceRoot } from '@nrwl/devkit'; + import { getTsPathAliasesApiExtractorConfig } from './utils'; type DeepPartial = Partial<{ [P in keyof T]: DeepPartial }>; @@ -12,7 +16,6 @@ describe(`utils`, () => { ...options.tsConfig?.compilerOptions, }, }, - tsConfigPath: 'tsconfig.lib.json', packageJson: { name: '@proj/one', version: '0.0.1', @@ -21,6 +24,7 @@ describe(`utils`, () => { peerDependencies: { ...options.packageJson?.peerDependencies }, }, definitionsRootPath: options.definitionsRootPath ?? 'dist/types', + pathAliasesTsConfigPath: options.pathAliasesTsConfigPath ?? undefined, }; return getTsPathAliasesApiExtractorConfig(defaults as Options); @@ -34,8 +38,19 @@ describe(`utils`, () => { ); }); + it(`should not use path aliases to emitted declaration files`, () => { + const actual = setup({ + definitionsRootPath: 'dist/for/types', + }); + + expect(actual.overrideTsconfig.compilerOptions).toEqual(expect.objectContaining({ paths: undefined })); + }); + it(`should override path aliases to emitted declaration files instead of source files`, () => { - const actual = setup({ definitionsRootPath: 'dist/for/types' }); + const actual = setup({ + definitionsRootPath: 'dist/for/types', + pathAliasesTsConfigPath: path.join(workspaceRoot, 'tsconfig.base.json'), + }); const newPaths = (actual.overrideTsconfig.compilerOptions.paths as unknown) as Record; diff --git a/scripts/tasks/src/utils.ts b/scripts/tasks/src/utils.ts index f8012f06034afe..2547fd796f5550 100644 --- a/scripts/tasks/src/utils.ts +++ b/scripts/tasks/src/utils.ts @@ -1,7 +1,7 @@ import * as fs from 'fs'; import * as path from 'path'; -import { stripJsonComments, workspaceRoot } from '@nrwl/devkit'; +import { stripJsonComments } from '@nrwl/devkit'; import * as jju from 'jju'; import type { TscTaskOptions } from 'just-scripts'; @@ -50,26 +50,38 @@ function enableAllowSyntheticDefaultImports(options: { pkgJson: PackageJson }) { return shouldEnable ? { allowSyntheticDefaultImports: true } : null; } -const rootTsConfig = JSON.parse(fs.readFileSync(path.join(workspaceRoot, 'tsconfig.base.json'), 'utf-8')) as TsConfig; +function createNormalizedTsPaths(options: { definitionsRootPath: string; pathAliasesTsConfigPath: string }) { + type PathAliases = Record; + const { definitionsRootPath, pathAliasesTsConfigPath } = options; + const tsConfigRoot = JSON.parse(fs.readFileSync(pathAliasesTsConfigPath, 'utf-8')) as TsConfig; + const paths = (tsConfigRoot.compilerOptions.paths as unknown) as undefined | PathAliases; -function createNormalizedTsPaths(options: { definitionsRootPath: string; rootTsConfig: TsConfig }) { - const paths = (options.rootTsConfig.compilerOptions.paths as unknown) as Record; + if (!paths) { + throw new Error(`Provided "${pathAliasesTsConfigPath}" has no compilerOptions.path defined`); + } const normalizedPaths = Object.entries(paths).reduce((acc, [pkgName, pathAliases]) => { - acc[pkgName] = [path.join(options.definitionsRootPath, pathAliases[0].replace('index.ts', 'index.d.ts'))]; + acc[pkgName] = [path.join(definitionsRootPath, pathAliases[0].replace('index.ts', 'index.d.ts'))]; return acc; - }, {} as typeof paths); + }, {} as PathAliases); return normalizedPaths; } export function getTsPathAliasesApiExtractorConfig(options: { tsConfig: TsConfig; - tsConfigPath: string; packageJson: PackageJson; definitionsRootPath: string; + pathAliasesTsConfigPath?: string; }) { - const normalizedPaths = createNormalizedTsPaths({ definitionsRootPath: options.definitionsRootPath, rootTsConfig }); + const { packageJson, tsConfig, pathAliasesTsConfigPath, definitionsRootPath } = options; + /** + * Because api-extractor ran into race conditions when executing via lage (https://github.com/microsoft/fluentui/issues/25766), + * we won't use path aliases on CI, rather serving api-extractor rolluped dts files cross package, that will be referenced via yarn workspace sym-links + */ + const normalizedPaths = pathAliasesTsConfigPath + ? createNormalizedTsPaths({ definitionsRootPath, pathAliasesTsConfigPath }) + : undefined; /** * Customized TSConfig that uses `tsconfig.lib.json` as base with some required overrides: @@ -77,14 +89,14 @@ export function getTsPathAliasesApiExtractorConfig(options: { * NOTES: * - `extends` is properly resolved via api-extractor which uses TS api * - `skipLibCheck` needs to be explicitly set to `false` so errors propagate to api-extractor - * - `paths` is overriden to path mapping that points to generated declaration files. This also enables creation of dts rollup without a need of generating rollups for all dependencies 🫡 + * - `paths` if usePathAliases is enabled, we override it to path mapping that points to generated declaration files. This also enables creation of dts rollup without a need of generating rollups for all dependencies 🫡 * */ const apiExtractorTsConfig: TsConfig = { - ...options.tsConfig, + ...tsConfig, compilerOptions: { - ...options.tsConfig.compilerOptions, - ...enableAllowSyntheticDefaultImports({ pkgJson: options.packageJson }), + ...tsConfig.compilerOptions, + ...enableAllowSyntheticDefaultImports({ pkgJson: packageJson }), /** * This option has no effect on type declarations '.d.ts' thus can be turned off. For more info see https://www.typescriptlang.org/tsconfig#non-module-files *