Skip to content

Commit

Permalink
fix(bazel): continue to support non-exports resolution in api-golden …
Browse files Browse the repository at this point in the history
…test

This is needed since the CLI does not use exports yet. See:
angular/angular-cli#22889
  • Loading branch information
devversion committed Mar 24, 2022
1 parent 6e4b153 commit 38f2a98
Show file tree
Hide file tree
Showing 15 changed files with 154 additions and 19 deletions.
1 change: 1 addition & 0 deletions bazel/api-golden/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ ts_library(
"find_entry_points.ts",
"index.ts",
"index_npm_packages.ts",
"path-normalize.ts",
"test_api_report.ts",
],
# A tsconfig needs to be specified as otherwise `ts_library` will look for the config
Expand Down
68 changes: 52 additions & 16 deletions bazel/api-golden/find_entry_points.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,47 +6,83 @@
* found in the LICENSE file at https://angular.io/license
*/

import {join, normalize} from 'path';
import {dirname, join, relative} from 'path';
import {lstatSync, readFileSync, readdirSync} from 'fs';

import {readFileSync} from 'fs';
import {PackageJson} from './index_npm_packages';

/** Interface describing a resolved NPM package entry point. */
export interface PackageEntryPoint {
typesEntryPointPath: string;
subpath: string;
moduleName: string;
}

/** Interface describing contents of a `package.json`. */
interface PackageJson {
name: string;
exports?: Record<string, {types?: string}>;
}

/** Finds all entry points within a given NPM package directory. */
export function findEntryPointsWithinNpmPackage(
dirPath: string,
packageJsonPath: string,
packageJson: PackageJson,
): PackageEntryPoint[] {
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')) as PackageJson;
const entryPoints: PackageEntryPoint[] = [];

// Legacy behavior to support Angular packages without the `exports` field.
// TODO: Remove when https://github.com/angular/angular-cli/issues/22889 is resolved.
if (packageJson.exports === undefined) {
throw new Error(
`Expected top-level "package.json" in "${dirPath}" to declare entry-points ` +
`through conditional exports.`,
);
return findEntryPointsByThroughNestedPackageFiles(dirPath);
}

for (const [subpath, conditions] of Object.entries(packageJson.exports)) {
if (conditions.types !== undefined) {
entryPoints.push({
subpath,
moduleName: normalize(join(packageJson.name, subpath)).replace(/\\/g, '/'),
typesEntryPointPath: join(dirPath, conditions.types),
});
}
}

return entryPoints;
}

/**
* Finds all entry points within a given NPM package directory by looking
* for nested `package.json` files. This is a legacy mechanism since we can
* consult the `package.json` `exports` information in most cases.
*/
function findEntryPointsByThroughNestedPackageFiles(dirPath: string): PackageEntryPoint[] {
const entryPoints: PackageEntryPoint[] = [];

for (const packageJsonFilePath of findPackageJsonFilesInDirectory(dirPath)) {
const packageJson = JSON.parse(readFileSync(packageJsonFilePath, 'utf8')) as PackageJson;
const subpath = relative(dirPath, dirname(packageJsonFilePath));
const typesFile = packageJson.types || packageJson.typings;

if (typesFile) {
entryPoints.push({
subpath,
typesEntryPointPath: join(dirname(packageJsonFilePath), typesFile),
});
}
}

return entryPoints;
}

/** Determine if the provided path is a directory. */
function isDirectory(dirPath: string) {
try {
return lstatSync(dirPath).isDirectory();
} catch {
return false;
}
}

/** Finds all `package.json` files within a directory. */
function* findPackageJsonFilesInDirectory(directoryPath: string): IterableIterator<string> {
for (const fileName of readdirSync(directoryPath)) {
const fullPath = join(directoryPath, fileName);
if (isDirectory(fullPath)) {
yield* findPackageJsonFilesInDirectory(fullPath);
} else if (fileName === 'package.json') {
yield fullPath;
}
}
}
17 changes: 15 additions & 2 deletions bazel/api-golden/index_npm_packages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,19 @@ import * as chalk from 'chalk';

import {findEntryPointsWithinNpmPackage} from './find_entry_points';
import {join} from 'path';
import {normalizePathToPosix} from './path-normalize';
import {readFileSync} from 'fs';
import {runfiles} from '@bazel/runfiles';
import {testApiGolden} from './test_api_report';

/** Interface describing contents of a `package.json`. */
export interface PackageJson {
name: string;
exports?: Record<string, {types?: string}>;
types?: string;
typings?: string;
}

/**
* Entry point for the `api_golden_test_npm_package` Bazel rule. This function determines
* all types within the specified NPM package and builds API reports that will be compared
Expand All @@ -26,17 +36,20 @@ async function main(
typeNames: string[],
) {
const packageJsonPath = join(npmPackageDir, 'package.json');
const entryPoints = findEntryPointsWithinNpmPackage(npmPackageDir, packageJsonPath);
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')) as PackageJson;
const entryPoints = findEntryPointsWithinNpmPackage(npmPackageDir, packageJson);
const outdatedGoldens: string[] = [];

let allTestsSucceeding = true;

for (const {subpath, moduleName, typesEntryPointPath} of entryPoints) {
for (const {subpath, typesEntryPointPath} of entryPoints) {
// API extractor generates API reports as markdown files. For each types
// entry-point we maintain a separate golden file. These golden files are
// based on the name of the defining NodeJS exports subpath in the NPM package,
// See: https://api-extractor.com/pages/overview/demo_api_report/.
const goldenName = join(subpath, 'index.md');
const goldenFilePath = join(goldenDir, goldenName);
const moduleName = normalizePathToPosix(join(packageJson.name, subpath));

const {succeeded, apiReportChanged} = await testApiGolden(
goldenFilePath,
Expand Down
14 changes: 14 additions & 0 deletions bazel/api-golden/path-normalize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {normalize} from 'path';

/** Normalizes a path to use Posix forward-slash separators. */
export function normalizePathToPosix(input: string): string {
return normalize(input).replace(/\\/g, '/');
}
10 changes: 10 additions & 0 deletions bazel/api-golden/test/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,13 @@ api_golden_test_npm_package(
golden_dir = "dev-infra/bazel/api-golden/test/goldens/test_package",
npm_package = "dev-infra/bazel/api-golden/test/fixtures/test_package",
)

api_golden_test_npm_package(
name = "test_npm_package_no_exports_field",
data = [
"goldens/pkg_no_exports_field",
"//bazel/api-golden/test/fixtures:pkg_no_exports_field",
],
golden_dir = "dev-infra/bazel/api-golden/test/goldens/pkg_no_exports_field",
npm_package = "dev-infra/bazel/api-golden/test/fixtures/pkg_no_exports_field",
)
6 changes: 5 additions & 1 deletion bazel/api-golden/test/fixtures/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ load("//tools:defaults.bzl", "ts_library")

package(default_visibility = ["//bazel/api-golden/test:__pkg__"])

exports_files(["test_package"])
# Expose these two fixture directories as tree artifacts.
exports_files([
"test_package",
"pkg_no_exports_field",
])

ts_library(
name = "test_lib",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export declare const fromTopLevel: boolean;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "test-package",
"type": "module",
"types": "./index.d.ts"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import {fromTopLevel} from 'test-package';

export declare const anotherVariable = fromTopLevel;

export declare function rawStringFn(input: string): string;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export declare const nestedFile = true;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"types": "../nested.d.ts"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"types": "./index.d.ts"
}
12 changes: 12 additions & 0 deletions bazel/api-golden/test/goldens/pkg_no_exports_field/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
## API Report File for "test-package"

> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts

// @public (undocumented)
export const fromTopLevel: boolean;

// (No @packageDocumentation comment for this package)

```
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
## API Report File for "test-package_testing"

> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts

// @public (undocumented)
export const anotherVariable = fromTopLevel;

// @public (undocumented)
export function rawStringFn(input: string): string;

// (No @packageDocumentation comment for this package)

```
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
## API Report File for "test-package_testing_nested"

> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts

// @public (undocumented)
export const nestedFile = true;

// (No @packageDocumentation comment for this package)

```

0 comments on commit 38f2a98

Please sign in to comment.