+Update the paths logic in the api reports command to support complex subpaths
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/repo-tools': patch
|
||||
---
|
||||
|
||||
Update the paths logic in the api reports command to support complex subpaths
|
||||
@@ -62,7 +62,7 @@ import { IMarkdownEmitterContext } from '@microsoft/api-documenter/lib/markdown/
|
||||
import { AstDeclaration } from '@microsoft/api-extractor/lib/analyzer/AstDeclaration';
|
||||
import { paths as cliPaths } from '../../lib/paths';
|
||||
import { minimatch } from 'minimatch';
|
||||
import { getPackageExportNames } from '../../lib/entryPoints';
|
||||
import { getPackageExportDetails } from '../../lib/getPackageExportDetails';
|
||||
import { createBinRunner } from '../util';
|
||||
|
||||
const tmpDir = cliPaths.resolveTargetRoot(
|
||||
@@ -303,8 +303,15 @@ function logApiReportInstructions() {
|
||||
|
||||
async function findPackageEntryPoints(packageDirs: string[]): Promise<
|
||||
Array<{
|
||||
// package dir relative to root, e.g. "packages/backend-app-api"
|
||||
packageDir: string;
|
||||
// the name of the export, e.g. "index" or "alpha"
|
||||
name: string;
|
||||
// the path within the dist directory for this export, e.g. "alpha.d.ts"
|
||||
distPath: string;
|
||||
// the path within the dist-types directory of this package for this export,
|
||||
// e.g. "src/entrypoints/foo/index.d.ts"
|
||||
distTypesPath: string;
|
||||
}>
|
||||
> {
|
||||
return Promise.all(
|
||||
@@ -313,12 +320,9 @@ async function findPackageEntryPoints(packageDirs: string[]): Promise<
|
||||
cliPaths.resolveTargetRoot(packageDir, 'package.json'),
|
||||
);
|
||||
|
||||
return (
|
||||
getPackageExportNames(pkg)?.map(name => ({ packageDir, name })) ?? {
|
||||
packageDir,
|
||||
name: 'index',
|
||||
}
|
||||
);
|
||||
return getPackageExportDetails(pkg).map(details => {
|
||||
return { packageDir, ...details };
|
||||
});
|
||||
}),
|
||||
).then(results => results.flat());
|
||||
}
|
||||
@@ -344,13 +348,23 @@ export async function runApiExtraction({
|
||||
}: ApiExtractionOptions) {
|
||||
await fs.remove(outputDir);
|
||||
|
||||
const packageEntryPoints = await findPackageEntryPoints(packageDirs);
|
||||
// The collection of all entry points of all packages, as a single list
|
||||
const allEntryPoints = await findPackageEntryPoints(packageDirs);
|
||||
|
||||
const entryPoints = packageEntryPoints.map(({ packageDir, name }) => {
|
||||
return cliPaths.resolveTargetRoot(
|
||||
`./dist-types/${packageDir}/src/${name}.d.ts`,
|
||||
);
|
||||
});
|
||||
// The path (relative to the root) to ALL dist-types entry points (e.g.
|
||||
// "dist-types/packages/backend-app-api/src/index.d.ts"). These are used as
|
||||
// "extra"/contextual entry points for the extractor so that it can see the
|
||||
// full context of things that are required by the local entry point being
|
||||
// inspected.
|
||||
const allDistTypesEntryPointPaths = allEntryPoints.map(
|
||||
({ packageDir, distTypesPath }) => {
|
||||
return cliPaths.resolveTargetRoot(
|
||||
'./dist-types',
|
||||
packageDir,
|
||||
distTypesPath,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
let compilerState: CompilerState | undefined = undefined;
|
||||
|
||||
@@ -364,8 +378,8 @@ export async function runApiExtraction({
|
||||
}
|
||||
const warnings = new Array<string>();
|
||||
|
||||
for (const [packageDir, group] of Object.entries(
|
||||
groupBy(packageEntryPoints, ep => ep.packageDir),
|
||||
for (const [packageDir, packageEntryPoints] of Object.entries(
|
||||
groupBy(allEntryPoints, ep => ep.packageDir),
|
||||
)) {
|
||||
console.log(`## Processing ${packageDir}`);
|
||||
const noBail = Array.isArray(allowWarnings)
|
||||
@@ -377,7 +391,6 @@ export async function runApiExtraction({
|
||||
'./dist-types',
|
||||
packageDir,
|
||||
);
|
||||
const names = group.map(ep => ep.name);
|
||||
|
||||
const remainingReportFiles = new Set(
|
||||
fs
|
||||
@@ -389,8 +402,9 @@ export async function runApiExtraction({
|
||||
),
|
||||
);
|
||||
|
||||
for (const name of names) {
|
||||
const suffix = name === 'index' ? '' : `-${name}`;
|
||||
for (const packageEntryPoint of packageEntryPoints) {
|
||||
const suffix =
|
||||
packageEntryPoint.name === 'index' ? '' : `-${packageEntryPoint.name}`;
|
||||
const reportFileName = `api-report${suffix}.md`;
|
||||
const reportPath = resolvePath(projectFolder, reportFileName);
|
||||
remainingReportFiles.delete(reportFileName);
|
||||
@@ -401,7 +415,7 @@ export async function runApiExtraction({
|
||||
configObject: {
|
||||
mainEntryPointFilePath: resolvePath(
|
||||
packageFolder,
|
||||
`src/${name}.d.ts`,
|
||||
packageEntryPoint.distTypesPath,
|
||||
),
|
||||
bundledPackages: [],
|
||||
|
||||
@@ -422,7 +436,7 @@ export async function runApiExtraction({
|
||||
docModel: {
|
||||
// TODO(Rugvip): This skips docs for non-index entry points. We can try to work around it, but
|
||||
// most likely it makes sense to wait for API Extractor to natively support exports.
|
||||
enabled: name === 'index',
|
||||
enabled: packageEntryPoint.name === 'index',
|
||||
apiJsonFilePath: resolvePath(
|
||||
outputDir,
|
||||
`<unscopedPackageName>${suffix}.api.json`,
|
||||
@@ -482,7 +496,7 @@ export async function runApiExtraction({
|
||||
|
||||
if (!compilerState) {
|
||||
compilerState = CompilerState.create(extractorConfig, {
|
||||
additionalEntryPoints: entryPoints,
|
||||
additionalEntryPoints: allDistTypesEntryPointPaths,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -519,18 +533,21 @@ export async function runApiExtraction({
|
||||
});
|
||||
|
||||
// This release tag validation makes sure that the release tag of known entry points match expectations.
|
||||
// The root index entrypoint is only allowed @public exports, while /alpha and /beta only allow @alpha and @beta.
|
||||
// The root index entry point is only allowed @public exports, while /alpha and /beta only allow @alpha and @beta.
|
||||
if (
|
||||
validateReleaseTags &&
|
||||
fs.pathExistsSync(extractorConfig.reportFilePath)
|
||||
) {
|
||||
if (['index', 'alpha', 'beta'].includes(name)) {
|
||||
if (['index', 'alpha', 'beta'].includes(packageEntryPoint.name)) {
|
||||
const report = await fs.readFile(
|
||||
extractorConfig.reportFilePath,
|
||||
'utf8',
|
||||
);
|
||||
const lines = report.split(/\r?\n/);
|
||||
const expectedTag = name === 'index' ? 'public' : name;
|
||||
const expectedTag =
|
||||
packageEntryPoint.name === 'index'
|
||||
? 'public'
|
||||
: packageEntryPoint.name;
|
||||
for (let i = 0; i < lines.length; i += 1) {
|
||||
const line = lines[i];
|
||||
const match = line.match(/^\/\/ @(alpha|beta|public)/);
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import fs from 'fs-extra';
|
||||
import { spawnSync } from 'child_process';
|
||||
import { paths as cliPaths } from '../../lib/paths';
|
||||
@@ -27,7 +28,6 @@ import { paths as cliPaths } from '../../lib/paths';
|
||||
* @param tsconfigFilePath {string} The path to the `tsconfig.json` file to use for generating the declaration files.
|
||||
* @returns {Promise<void>} A promise that resolves when the declaration files have been generated.
|
||||
*/
|
||||
|
||||
export async function generateTypeDeclarations(tsconfigFilePath: string) {
|
||||
await fs.remove(cliPaths.resolveTargetRoot('dist-types'));
|
||||
const { status } = spawnSync(
|
||||
|
||||
@@ -20,7 +20,7 @@ import { resolve as resolvePath } from 'path';
|
||||
// eslint-disable-next-line @backstage/no-undeclared-imports
|
||||
import chalk from 'chalk';
|
||||
import { getPackages, Package } from '@manypkg/get-packages';
|
||||
import { getPackageExportNames } from '../../lib/entryPoints';
|
||||
import { getPackageExportDetails } from '../../lib/getPackageExportDetails';
|
||||
|
||||
export default async () => {
|
||||
const { packages } = await getPackages(resolvePath('.'));
|
||||
@@ -97,11 +97,9 @@ function findAllDeps(declSrc: string) {
|
||||
* missing or incorrect in package.json
|
||||
*/
|
||||
function checkTypes(pkg: Package) {
|
||||
const entryPointNames = getPackageExportNames(pkg.packageJson) ?? ['index'];
|
||||
|
||||
const allDeps = entryPointNames.flatMap(name => {
|
||||
const allDeps = getPackageExportDetails(pkg.packageJson).flatMap(exp => {
|
||||
const typeDecl = fs.readFileSync(
|
||||
resolvePath(pkg.dir, `dist/${name}.d.ts`),
|
||||
resolvePath(pkg.dir, 'dist', exp.distPath),
|
||||
'utf8',
|
||||
);
|
||||
return findAllDeps(typeDecl);
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { extname } from 'path';
|
||||
import type { JsonObject } from '@backstage/types';
|
||||
|
||||
export function getPackageExportNames(pkg: JsonObject): string[] | undefined {
|
||||
if (pkg.exports && typeof pkg.exports !== 'string') {
|
||||
return Object.entries(pkg.exports).flatMap(([mount, path]) => {
|
||||
const ext = extname(String(path));
|
||||
if (!['.ts', '.tsx', '.cts', '.mts'].includes(ext)) {
|
||||
return []; // Ignore non-TS entry points
|
||||
}
|
||||
let name = mount;
|
||||
if (name.startsWith('./')) {
|
||||
name = name.slice(2);
|
||||
}
|
||||
if (!name || name === '.') {
|
||||
return ['index'];
|
||||
}
|
||||
return [name];
|
||||
});
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { extname } from 'path';
|
||||
import type { JsonObject } from '@backstage/types';
|
||||
|
||||
export function getPackageExportDetails(pkg: JsonObject): Array<{
|
||||
// the name of the export, e.g. "index" or "alpha"
|
||||
name: string;
|
||||
// the path within the dist directory for this export, e.g. "alpha.d.ts"
|
||||
distPath: string;
|
||||
// the path within the dist-types directory of this package for this export,
|
||||
// e.g. "src/entrypoints/foo/index.d.ts"
|
||||
distTypesPath: string;
|
||||
}> {
|
||||
if (pkg.exports && typeof pkg.exports !== 'string') {
|
||||
return Object.entries(pkg.exports).flatMap(
|
||||
([mount, path]: [string, string]) => {
|
||||
const ext = extname(path);
|
||||
if (!['.ts', '.tsx', '.cts', '.mts'].includes(ext)) {
|
||||
return []; // Ignore non-TS entry points
|
||||
}
|
||||
|
||||
let name = mount;
|
||||
if (name.startsWith('./')) {
|
||||
name = name.slice(2);
|
||||
}
|
||||
if (!name || name === '.') {
|
||||
name = 'index';
|
||||
}
|
||||
|
||||
const distPath = `${name}.d.ts`;
|
||||
const distTypesPath = path
|
||||
.replace(/^\.\//, '') // Remove leading "./"
|
||||
.replace(/\.[^.]+$/, '.d.ts'); // Replace .extension with .d.ts
|
||||
|
||||
return [
|
||||
{
|
||||
name,
|
||||
distPath,
|
||||
distTypesPath,
|
||||
},
|
||||
];
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
name: 'index',
|
||||
distPath: 'index.d.ts',
|
||||
distTypesPath: 'src/index.d.ts',
|
||||
},
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user