豆豆友情提示:这是一个非官方 GitHub 代理镜像,主要用于网络测试或访问加速。请勿在此进行登录、注册或处理任何敏感信息。进行这些操作请务必访问官方网站 github.com。 Raw 内容也通过此代理提供。
Skip to content

Commit 6379fcf

Browse files
committed
feat(bazel): api-golden test should leverage package exports information for finding entries
Instead of looking for nested `package.json` files which currently only exist for workaround-reasons in APF v13, we should consult the package NodeJS exports. This will help with: angular/angular#45405.
1 parent be344b8 commit 6379fcf

File tree

3 files changed

+46
-48
lines changed

3 files changed

+46
-48
lines changed

bazel/api-golden/find_entry_points.ts

Lines changed: 23 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,57 +6,47 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {lstatSync, readdirSync, readFileSync} from 'fs';
10-
import {dirname, join} from 'path';
9+
import {join, normalize} from 'path';
10+
11+
import {readFileSync} from 'fs';
1112

1213
/** Interface describing a resolved NPM package entry point. */
1314
export interface PackageEntryPoint {
1415
typesEntryPointPath: string;
15-
packageJsonPath: string;
16+
subpath: string;
17+
moduleName: string;
1618
}
1719

1820
/** Interface describing contents of a `package.json`. */
1921
interface PackageJson {
20-
types?: string;
21-
typings?: string;
22+
name: string;
23+
exports?: Record<string, {types?: string}>;
2224
}
2325

2426
/** Finds all entry points within a given NPM package directory. */
25-
export function findEntryPointsWithinNpmPackage(dirPath: string): PackageEntryPoint[] {
27+
export function findEntryPointsWithinNpmPackage(
28+
dirPath: string,
29+
packageJsonPath: string,
30+
): PackageEntryPoint[] {
31+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')) as PackageJson;
2632
const entryPoints: PackageEntryPoint[] = [];
2733

28-
for (const packageJsonFilePath of findPackageJsonFilesInDirectory(dirPath)) {
29-
const packageJson = JSON.parse(readFileSync(packageJsonFilePath, 'utf8')) as PackageJson;
30-
const typesFile = packageJson.types || packageJson.typings;
34+
if (packageJson.exports === undefined) {
35+
throw new Error(
36+
`Expected top-level "package.json" in "${dirPath}" to declare entry-points ` +
37+
`through conditional exports.`,
38+
);
39+
}
3140

32-
if (typesFile) {
41+
for (const [subpath, conditions] of Object.entries(packageJson.exports)) {
42+
if (conditions.types !== undefined) {
3343
entryPoints.push({
34-
packageJsonPath: packageJsonFilePath,
35-
typesEntryPointPath: join(dirname(packageJsonFilePath), typesFile),
44+
subpath,
45+
moduleName: normalize(join(packageJson.name, subpath)).replace(/\\/g, '/'),
46+
typesEntryPointPath: join(dirPath, conditions.types),
3647
});
3748
}
3849
}
3950

4051
return entryPoints;
4152
}
42-
43-
/** Determine if the provided path is a directory. */
44-
function isDirectory(dirPath: string) {
45-
try {
46-
return lstatSync(dirPath).isDirectory();
47-
} catch {
48-
return false;
49-
}
50-
}
51-
52-
/** Finds all `package.json` files within a directory. */
53-
function* findPackageJsonFilesInDirectory(directoryPath: string): IterableIterator<string> {
54-
for (const fileName of readdirSync(directoryPath)) {
55-
const fullPath = join(directoryPath, fileName);
56-
if (isDirectory(fullPath)) {
57-
yield* findPackageJsonFilesInDirectory(fullPath);
58-
} else if (fileName === 'package.json') {
59-
yield fullPath;
60-
}
61-
}
62-
}

bazel/api-golden/index_npm_packages.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {runfiles} from '@bazel/runfiles';
109
import * as chalk from 'chalk';
11-
import {join, relative} from 'path';
1210

1311
import {findEntryPointsWithinNpmPackage} from './find_entry_points';
12+
import {join} from 'path';
13+
import {runfiles} from '@bazel/runfiles';
1414
import {testApiGolden} from './test_api_report';
1515

1616
/**
@@ -25,18 +25,17 @@ async function main(
2525
stripExportPattern: RegExp,
2626
typeNames: string[],
2727
) {
28-
const entryPoints = findEntryPointsWithinNpmPackage(npmPackageDir);
28+
const packageJsonPath = join(npmPackageDir, 'package.json');
29+
const entryPoints = findEntryPointsWithinNpmPackage(npmPackageDir, packageJsonPath);
2930
const outdatedGoldens: string[] = [];
3031
let allTestsSucceeding = true;
3132

32-
for (const {packageJsonPath, typesEntryPointPath} of entryPoints) {
33-
const pkgRelativeName = relative(npmPackageDir, typesEntryPointPath);
33+
for (const {subpath, moduleName, typesEntryPointPath} of entryPoints) {
3434
// API extractor generates API reports as markdown files. For each types
3535
// entry-point we maintain a separate golden file. These golden files are
36-
// based on the name of the entry-point `.d.ts` file in the NPM package,
37-
// but with the proper `.md` file extension.
36+
// based on the name of the defining NodeJS exports subpath in the NPM package,
3837
// See: https://api-extractor.com/pages/overview/demo_api_report/.
39-
const goldenName = pkgRelativeName.replace(/\.d\.ts$/, '.md');
38+
const goldenName = join(subpath, 'index.md');
4039
const goldenFilePath = join(goldenDir, goldenName);
4140

4241
const {succeeded, apiReportChanged} = await testApiGolden(
@@ -46,6 +45,7 @@ async function main(
4645
stripExportPattern,
4746
typeNames,
4847
packageJsonPath,
48+
moduleName,
4949
);
5050

5151
// Keep track of outdated goldens.

bazel/api-golden/test_api_report.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {runfiles} from '@bazel/runfiles';
109
import {
1110
ConsoleMessageId,
1211
Extractor,
@@ -17,9 +16,11 @@ import {
1716
ExtractorResult,
1817
IConfigFile,
1918
} from '@microsoft/api-extractor';
19+
import {basename, dirname} from 'path';
20+
2021
import {AstModule} from '@microsoft/api-extractor/lib/analyzer/AstModule';
2122
import {ExportAnalyzer} from '@microsoft/api-extractor/lib/analyzer/ExportAnalyzer';
22-
import {basename, dirname} from 'path';
23+
import {runfiles} from '@bazel/runfiles';
2324

2425
/**
2526
* Original definition of the `ExportAnalyzer#fetchAstModuleExportInfo` method.
@@ -42,6 +43,9 @@ const _origFetchAstModuleExportInfo = ExportAnalyzer.prototype.fetchAstModuleExp
4243
* @param packageJsonPath Optional path to a `package.json` file that contains the entry
4344
* point. Note that the `package.json` is currently only used by `api-extractor` to determine
4445
* the package name displayed within the API golden.
46+
* @param customPackageName A custom package name to be provided for the API report. This can be
47+
* useful when the specified `package.json` is describing the whole package but the API report
48+
* is scoped to a specific subpath/entry-point.
4549
*/
4650
export async function testApiGolden(
4751
goldenFilePath: string,
@@ -50,6 +54,7 @@ export async function testApiGolden(
5054
stripExportPattern: RegExp,
5155
typeNames: string[] = [],
5256
packageJsonPath = resolveWorkspacePackageJsonPath(),
57+
customPackageName?: string,
5358
): Promise<ExtractorResult> {
5459
// If no `TEST_TMPDIR` is defined, then this script runs using `bazel run`. We use
5560
// the runfile directory as temporary directory for API extractor.
@@ -90,11 +95,14 @@ export async function testApiGolden(
9095
// compatible with the API extractor. This is a workaround for a bug in api-extractor.
9196
// TODO remove once https://github.com/microsoft/rushstack/issues/2774 is resolved.
9297
const packageJson = require(packageJsonPath);
93-
const packageNameSegments = packageJson.name.split('/');
94-
const packageName =
95-
packageNameSegments.length === 1
96-
? packageNameSegments[0]
97-
: `${packageNameSegments[0]}/${packageNameSegments.slice(1).join('_')}`;
98+
const packageNameSegments = (customPackageName ?? packageJson.name).split('/');
99+
const isScopedPackage = packageNameSegments[0][0] === '@' && packageNameSegments.length > 1;
100+
// API extractor allows one-slash when the package uses the scoped-package convention.
101+
const slashConversionStartIndex = isScopedPackage ? 1 : 0;
102+
const normalizedRest = packageNameSegments.slice(slashConversionStartIndex).join('_');
103+
const packageName = isScopedPackage
104+
? `${packageNameSegments[0]}/${normalizedRest}`
105+
: normalizedRest;
98106

99107
const extractorConfig = ExtractorConfig.prepare({
100108
configObject,

0 commit comments

Comments
 (0)