diff --git a/scripts/tasks/src/api-extractor.ts b/scripts/tasks/src/api-extractor.ts index 68ae9021cfde9..7902aae3ad81c 100644 --- a/scripts/tasks/src/api-extractor.ts +++ b/scripts/tasks/src/api-extractor.ts @@ -1,29 +1,13 @@ -import * as glob from 'glob'; import * as path from 'path'; -import { apiExtractorVerifyTask, task, series, logger, TaskFunction } from 'just-scripts'; -import { ExtractorMessageCategory } from '@microsoft/api-extractor'; -import chalk from 'chalk'; -import { getTsPathAliasesConfig, getTsPathAliasesApiExtractorConfig } from './utils'; -import { getJustArgv } from './argv'; -const apiExtractorConfigs: Array< - [ - configPath: string, - /** - * config name is created from suffix `api-extractor..json`. - * @example - * `api-extractor.fast.json -> configName === fast` - * - * default behavior: - * `api-extractor.json -> configName === default` - */ - configName: string, - ] -> = glob - .sync(path.join(process.cwd(), 'config/api-extractor*.json')) - .map(configPath => [configPath, configPath.replace(/.*\bapi-extractor(?:\.(.*))?\.json$/, '$1') || 'default']); +import type { ExtractorMessageCategory, ExtractorResult } from '@microsoft/api-extractor'; +import chalk from 'chalk'; +import * as glob from 'glob'; +import { ApiExtractorOptions, TaskFunction, apiExtractorVerifyTask, logger, series, task } from 'just-scripts'; +import type * as ApiExtractorTypes from 'just-scripts/src/tasks/apiExtractorTypes'; -const apiExtractorConfigsForExecution = apiExtractorConfigs.filter(([, configName]) => configName !== 'local'); +import { getJustArgv } from './argv'; +import { getTsPathAliasesApiExtractorConfig, getTsPathAliasesConfig } from './utils'; const compilerMessages = { /** @@ -63,111 +47,155 @@ interface ApiExtractorCliRunCommandArgs { 'typescript-compiler-folder': string; } -export function apiExtractor() { - const args: ReturnType & Partial = getJustArgv(); - - const { isUsingTsSolutionConfigs, packageJson, tsConfig, tsConfigPath } = getTsPathAliasesConfig(); +export function apiExtractor(): TaskFunction { + const { configs, configsToExecute } = getConfig(); const messages = { TS7016: [] as string[], TS2305: [] as string[], }; + let configDebug: Parameters>[0] | null = null; + + const args: ReturnType & Partial = getJustArgv(); + const { isUsingTsSolutionConfigs, packageJson, tsConfig, tsConfigPath } = getTsPathAliasesConfig(); + + if (configsToExecute.length === 0) { + return noop; + } + + const tasks = configsToExecute.map(([configPath, configName]) => { + const taskName = `api-extractor:${configName}`; + + task( + taskName, + + apiExtractorVerifyTask({ + showVerboseMessages: args.verbose, + showDiagnostics: args.diagnostics, + typescriptCompilerFolder: args['typescript-compiler-folder'], + configJsonFilePath: args.config ?? configPath, + localBuild: args.local ?? !process.env.TF_BUILD, + onConfigLoaded, + messageCallback, + onResult, + }), + ); + + return taskName; + }); + + return series(...tasks); + + function noop() { + if (configs.length) { + logger.info(`skipping api-extractor execution - no configs to execute present besides: '${configs}'`); + return; + } + + logger.info(`skipping api-extractor execution - no configs present`); + } + + function onConfigLoaded(config: Parameters>[0]) { + if (!(isUsingTsSolutionConfigs && tsConfig)) { + return; + } + + logger.info(`api-extractor: package is using TS path aliases. Overriding TS compiler settings.`); + + const compilerConfig = getTsPathAliasesApiExtractorConfig({ + tsConfig, + tsConfigPath, + packageJson, + definitionsRootPath: 'dist/out-tsc/types', + }); + + // NOTE: internally just-tasks calls `options.onConfigLoaded?.(rawConfig);` so we need to mutate object properties (js passes objects by reference) + config.compiler = compilerConfig; + + configDebug = config; + } + + function messageCallback(message: Parameters>[0]) { + if (!isUsingTsSolutionConfigs) { + return; + } + if (message.category !== messageCategories.Compiler) { + return; + } + + if (message.messageId === compilerMessages.TS2305) { + messages.TS2305.push(message.text); + } + + if (message.messageId === compilerMessages.TS7016) { + messages.TS7016.push(message.text); + } + } + + function onResult(result: ExtractorResult, _extractorOptions: ApiExtractorTypes.IExtractorInvokeOptions): void { + if (!isUsingTsSolutionConfigs) { + return; + } + + if (result.succeeded === true) { + return; + } + + // Log on CI processed configs for better troubleshooting for https://github.com/microsoft/fluentui/issues/25766 + // if (process.env.TF_BUILD) { + logger.info('❌ api-extractor FAIL debug:', { + configsToExecute, + extractorOptions: JSON.stringify(configDebug, null, 2), + }); + // } + + if (messages.TS2305.length) { + const errTitle = [ + chalk.bgRed.white.bold(`api-extractor | API VIOLATION:`), + chalk.red(` Your package public API uses \`@internal\` marked API's from following packages:`), + '\n', + ].join(''); + const logErr = formatApiViolationMessage(messages.TS2305); + + logger.error(errTitle, logErr, '\n'); + } + + if (messages.TS7016.length) { + const errTitle = [ + chalk.bgRed.white.bold(`api-extractor | MISSING DEPENDENCY TYPE DECLARATIONS:`), + chalk.red(` Package dependencies are missing index.d.ts type definitions:`), + '\n', + ].join(''); + const logErr = formatMissingApiViolationMessage(messages.TS7016); + const logFix = chalk.blueBright( + `${chalk.bold('🛠 FIX')}: run '${chalk.italic(`yarn lage generate-api --to ${packageJson.name}`)}'`, + ); + + logger.error(errTitle, logErr, '\n', logFix, '\n'); + } + } +} + +function getConfig() { + type Config = [ + configPath: string, + /** + * config name is created from suffix `api-extractor..json`. + * @example + * `api-extractor.fast.json -> configName === fast` + * + * default behavior: + * `api-extractor.json -> configName === default` + */ + configName: string, + ]; + + const configs: Config[] = glob + .sync(path.join(process.cwd(), 'config/api-extractor*.json')) + .map(configPath => [configPath, configPath.replace(/.*\bapi-extractor(?:\.(.*))?\.json$/, '$1') || 'default']); + + const configsToExecute = configs.filter(([, configName]) => configName !== 'local'); - return apiExtractorConfigsForExecution.length - ? (series( - ...apiExtractorConfigsForExecution.map(([configPath, configName]) => { - const taskName = `api-extractor:${configName}`; - - task( - taskName, - - apiExtractorVerifyTask({ - showVerboseMessages: args.verbose, - showDiagnostics: args.diagnostics, - typescriptCompilerFolder: args['typescript-compiler-folder'], - configJsonFilePath: args.config ?? configPath, - localBuild: args.local ?? !process.env.TF_BUILD, - onResult: result => { - if (!isUsingTsSolutionConfigs) { - return; - } - if (result.succeeded === true) { - return; - } - - if (messages.TS2305.length) { - const errTitle = [ - chalk.bgRed.white.bold(`api-extractor | API VIOLATION:`), - chalk.red(` Your package public API uses \`@internal\` marked API's from following packages:`), - '\n', - ].join(''); - const logErr = formatApiViolationMessage(messages.TS2305); - - logger.error(errTitle, logErr, '\n'); - } - - if (messages.TS7016.length) { - const errTitle = [ - chalk.bgRed.white.bold(`api-extractor | MISSING DEPENDENCY TYPE DECLARATIONS:`), - chalk.red(` Package dependencies are missing index.d.ts type definitions:`), - '\n', - ].join(''); - const logErr = formatMissingApiViolationMessage(messages.TS7016); - const logFix = chalk.blueBright( - `${chalk.bold('🛠 FIX')}: run '${chalk.italic(`yarn lage generate-api --to ${packageJson.name}`)}'`, - ); - - logger.error(errTitle, logErr, '\n', logFix, '\n'); - } - }, - - messageCallback: message => { - if (!isUsingTsSolutionConfigs) { - return; - } - if (message.category !== messageCategories.Compiler) { - return; - } - - if (message.messageId === compilerMessages.TS2305) { - messages.TS2305.push(message.text); - } - - if (message.messageId === compilerMessages.TS7016) { - messages.TS7016.push(message.text); - } - }, - onConfigLoaded: config => { - if (!(isUsingTsSolutionConfigs && tsConfig)) { - return; - } - - logger.info(`api-extractor: package is using TS path aliases. Overriding TS compiler settings.`); - - const compilerConfig = getTsPathAliasesApiExtractorConfig({ - tsConfig, - tsConfigPath, - packageJson, - definitionsRootPath: 'dist/out-tsc/types', - }); - - config.compiler = compilerConfig; - }, - }), - ); - - return taskName; - }), - ) as TaskFunction) - : () => { - if (apiExtractorConfigs.length) { - logger.info( - `skipping api-extractor execution - no configs to execute present besides: '${apiExtractorConfigs}'`, - ); - return; - } - - logger.info(`skipping api-extractor execution - no configs present`); - }; + return { configsToExecute, configs }; } /** diff --git a/scripts/tasks/src/ts.ts b/scripts/tasks/src/ts.ts index df18fd66d53a5..1d7e99665ff3b 100644 --- a/scripts/tasks/src/ts.ts +++ b/scripts/tasks/src/ts.ts @@ -1,5 +1,7 @@ import * as path from 'path'; -import { tscTask, TscTaskOptions, logger } from 'just-scripts'; + +import { TscTaskOptions, logger, tscTask } from 'just-scripts'; + import { getJustArgv } from './argv'; import { getTsPathAliasesConfig, getTsPathAliasesConfigV8 } from './utils'; @@ -38,16 +40,7 @@ function prepareTsTaskConfig(options: TscTaskOptions) { const tsConfigOutDir = tsConfig.compilerOptions.outDir as string; - const hasNewCompilationSetup = tsConfigOutDir.includes('dist/out-tsc'); - - if (hasNewCompilationSetup) { - options.outDir = `${tsConfigOutDir}/${options.outDir}`; - } else { - // TODO: remove after all v9 is migrated to new tsc processing - options.baseUrl = '.'; - options.rootDir = './src'; - } - + options.outDir = `${tsConfigOutDir}/${options.outDir}`; options.project = tsConfigFile; } diff --git a/scripts/tasks/src/utils.ts b/scripts/tasks/src/utils.ts index cc2ac2d20c508..f8012f06034af 100644 --- a/scripts/tasks/src/utils.ts +++ b/scripts/tasks/src/utils.ts @@ -69,13 +69,7 @@ export function getTsPathAliasesApiExtractorConfig(options: { packageJson: PackageJson; definitionsRootPath: string; }) { - const hasNewCompilationSetup = ((options.tsConfig.compilerOptions as unknown) as { outDir: string }).outDir.includes( - 'dist/out-tsc', - ); - // TODO: after all v9 is migrated to new tsc processing use only createNormalizedTsPaths - const normalizedPaths = hasNewCompilationSetup - ? createNormalizedTsPaths({ definitionsRootPath: options.definitionsRootPath, rootTsConfig }) - : undefined; + const normalizedPaths = createNormalizedTsPaths({ definitionsRootPath: options.definitionsRootPath, rootTsConfig }); /** * Customized TSConfig that uses `tsconfig.lib.json` as base with some required overrides: