Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
274 changes: 151 additions & 123 deletions scripts/tasks/src/api-extractor.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,13 @@
import * as glob from 'glob';
import * as path from 'path';
Comment thread
Hotell marked this conversation as resolved.
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 <configName> suffix `api-extractor.<configName>.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';
Comment thread
Hotell marked this conversation as resolved.

const apiExtractorConfigsForExecution = apiExtractorConfigs.filter(([, configName]) => configName !== 'local');
import { getJustArgv } from './argv';
import { getTsPathAliasesApiExtractorConfig, getTsPathAliasesConfig } from './utils';

const compilerMessages = {
/**
Expand Down Expand Up @@ -63,111 +47,155 @@ interface ApiExtractorCliRunCommandArgs {
'typescript-compiler-folder': string;
}

export function apiExtractor() {
const args: ReturnType<typeof getJustArgv> & Partial<ApiExtractorCliRunCommandArgs> = 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<NonNullable<ApiExtractorOptions['onConfigLoaded']>>[0] | null = null;

const args: ReturnType<typeof getJustArgv> & Partial<ApiExtractorCliRunCommandArgs> = 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<NonNullable<ApiExtractorOptions['onConfigLoaded']>>[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<NonNullable<ApiExtractorOptions['messageCallback']>>[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 <configName> suffix `api-extractor.<configName>.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 };
}

/**
Expand Down
15 changes: 4 additions & 11 deletions scripts/tasks/src/ts.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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;
}

Expand Down
8 changes: 1 addition & 7 deletions scripts/tasks/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down