Merge pull request #14668 from backstage/sharks/repo-tools
Introduction of repo-tool packages with `api-report`
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/repo-tools': minor
|
||||
---
|
||||
|
||||
Introducing repo-tools package
|
||||
+3
-6
@@ -11,7 +11,7 @@
|
||||
"build:backend": "yarn workspace backend build",
|
||||
"build:all": "backstage-cli repo build --all",
|
||||
"build:api-reports": "yarn build:api-reports:only --tsc",
|
||||
"build:api-reports:only": "ts-node -T -P scripts/tsconfig.json scripts/api-extractor.ts",
|
||||
"build:api-reports:only": "backstage-repo-tools api-reports",
|
||||
"build:api-docs": "LANG=en_EN yarn build:api-reports --docs",
|
||||
"tsc": "tsc",
|
||||
"tsc:full": "backstage-cli repo clean && tsc --skipLibCheck false --incremental false",
|
||||
@@ -49,16 +49,13 @@
|
||||
"version": "1.8.0",
|
||||
"dependencies": {
|
||||
"@backstage/errors": "workspace:^",
|
||||
"@manypkg/get-packages": "^1.1.3",
|
||||
"@microsoft/api-documenter": "^7.17.11",
|
||||
"@microsoft/api-extractor": "^7.23.0",
|
||||
"@microsoft/api-extractor-model": "^7.17.2",
|
||||
"@microsoft/tsdoc": "^0.14.1"
|
||||
"@manypkg/get-packages": "^1.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "workspace:*",
|
||||
"@backstage/codemods": "workspace:*",
|
||||
"@backstage/create-app": "workspace:*",
|
||||
"@backstage/repo-tools": "workspace:*",
|
||||
"@changesets/cli": "^2.14.0",
|
||||
"@octokit/rest": "^19.0.3",
|
||||
"@spotify/prettier-config": "^14.0.0",
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, {
|
||||
rules: {
|
||||
'no-console': 0,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
# @backstage/repo-tools
|
||||
|
||||
This package provides a CLI for backstage repo tooling.
|
||||
|
||||
## Installation
|
||||
|
||||
Install the package via Yarn:
|
||||
|
||||
```sh
|
||||
yarn add @backstage/repo-tools
|
||||
```
|
||||
Executable
+37
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env node
|
||||
/*
|
||||
* Copyright 2022 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.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
|
||||
// Figure out whether we're running inside the backstage repo or as an installed dependency
|
||||
/* eslint-disable-next-line no-restricted-syntax */
|
||||
const isLocal = require('fs').existsSync(path.resolve(__dirname, '../src'));
|
||||
|
||||
if (!isLocal || process.env.BACKSTAGE_E2E_CLI_TEST) {
|
||||
require('..');
|
||||
} else {
|
||||
require('ts-node').register({
|
||||
transpileOnly: true,
|
||||
/* eslint-disable-next-line no-restricted-syntax */
|
||||
project: path.resolve(__dirname, '../../../tsconfig.json'),
|
||||
compilerOptions: {
|
||||
module: 'CommonJS',
|
||||
},
|
||||
});
|
||||
|
||||
require('../src');
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
## CLI Report file for "@backstage/repo-tools"
|
||||
|
||||
> Do not edit this file. It is a report generated by `yarn build:api-reports`
|
||||
|
||||
### `backstage-repo-tools`
|
||||
|
||||
```
|
||||
Usage: backstage-repo-tools [options] [command]
|
||||
|
||||
Options:
|
||||
-V, --version
|
||||
-h, --help
|
||||
|
||||
Commands:
|
||||
api-reports [options] [path...]
|
||||
help [command]
|
||||
```
|
||||
|
||||
### `backstage-repo-tools api-reports`
|
||||
|
||||
```
|
||||
Usage: backstage-repo-tools api-reports [options] [path...]
|
||||
|
||||
Options:
|
||||
--ci
|
||||
--tsc
|
||||
--docs
|
||||
-h, --help
|
||||
```
|
||||
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "@backstage/repo-tools",
|
||||
"description": "CLI for Backstage repo tooling ",
|
||||
"version": "0.0.0",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"backstage": {
|
||||
"role": "cli"
|
||||
},
|
||||
"homepage": "https://backstage.io",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/backstage/backstage",
|
||||
"directory": "packages/repo-tools"
|
||||
},
|
||||
"keywords": [
|
||||
"backstage"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"main": "dist/index.cjs.js",
|
||||
"scripts": {
|
||||
"build": "backstage-cli package build",
|
||||
"lint": "backstage-cli package lint",
|
||||
"test": "backstage-cli package test",
|
||||
"clean": "backstage-cli package clean",
|
||||
"start": "nodemon --"
|
||||
},
|
||||
"bin": {
|
||||
"backstage-repo-tools": "bin/backstage-repo-tools"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/errors": "workspace:^",
|
||||
"@microsoft/api-documenter": "^7.17.11",
|
||||
"@microsoft/api-extractor": "^7.23.0",
|
||||
"@microsoft/api-extractor-model": "^7.17.2",
|
||||
"@microsoft/tsdoc": "0.14.1",
|
||||
"chalk": "^4.0.0",
|
||||
"commander": "^9.1.0",
|
||||
"fs-extra": "10.1.0",
|
||||
"ts-node": "^10.0.0"
|
||||
},
|
||||
"files": [
|
||||
"bin",
|
||||
"dist/**/*.js"
|
||||
],
|
||||
"nodemonConfig": {
|
||||
"watch": "./src",
|
||||
"exec": "bin/backstage-repo-tools",
|
||||
"ext": "ts"
|
||||
}
|
||||
}
|
||||
+63
-123
@@ -24,7 +24,7 @@ import {
|
||||
dirname,
|
||||
join,
|
||||
} from 'path';
|
||||
import { spawnSync, execFile } from 'child_process';
|
||||
import { execFile } from 'child_process';
|
||||
import prettier from 'prettier';
|
||||
import fs from 'fs-extra';
|
||||
import {
|
||||
@@ -49,11 +49,17 @@ import {
|
||||
import { DocTable } from '@microsoft/api-documenter/lib/nodes/DocTable';
|
||||
import { DocTableRow } from '@microsoft/api-documenter/lib/nodes/DocTableRow';
|
||||
import { DocHeading } from '@microsoft/api-documenter/lib/nodes/DocHeading';
|
||||
import { CustomMarkdownEmitter } from '@microsoft/api-documenter/lib/markdown/CustomMarkdownEmitter';
|
||||
import {
|
||||
CustomMarkdownEmitter,
|
||||
ICustomMarkdownEmitterOptions,
|
||||
} from '@microsoft/api-documenter/lib/markdown/CustomMarkdownEmitter';
|
||||
import { IMarkdownEmitterContext } from '@microsoft/api-documenter/lib/markdown/MarkdownEmitter';
|
||||
import { AstDeclaration } from '@microsoft/api-extractor/lib/analyzer/AstDeclaration';
|
||||
|
||||
const tmpDir = resolvePath(__dirname, '../node_modules/.cache/api-extractor');
|
||||
const tmpDir = resolvePath(
|
||||
process.cwd(),
|
||||
'./node_modules/.cache/api-extractor',
|
||||
);
|
||||
|
||||
/**
|
||||
* All of this monkey patching below is because MUI has these bare package.json file as a method
|
||||
@@ -99,7 +105,9 @@ function patchFileMessageFetcher(
|
||||
} = router;
|
||||
|
||||
router.fetchAssociatedMessagesForReviewFile =
|
||||
function patchedFetchAssociatedMessagesForReviewFile(ast) {
|
||||
function patchedFetchAssociatedMessagesForReviewFile(
|
||||
ast: AstDeclaration | undefined,
|
||||
) {
|
||||
const messages = fetchAssociatedMessagesForReviewFile.call(this, ast);
|
||||
return transform(messages, ast);
|
||||
};
|
||||
@@ -113,7 +121,10 @@ function patchFileMessageFetcher(
|
||||
const originalGenerateReviewFileContent =
|
||||
ApiReportGenerator.generateReviewFileContent;
|
||||
ApiReportGenerator.generateReviewFileContent =
|
||||
function decoratedGenerateReviewFileContent(collector, ...moreArgs) {
|
||||
function decoratedGenerateReviewFileContent(
|
||||
collector: { program: Program; messageRouter: any },
|
||||
...moreArgs: any[]
|
||||
) {
|
||||
const program = collector.program as Program;
|
||||
|
||||
// The purpose of this override is to allow the @ignore tag to be used to ignore warnings
|
||||
@@ -137,7 +148,9 @@ ApiReportGenerator.generateReviewFileContent =
|
||||
}
|
||||
const [, symbolName] = symbolMatch;
|
||||
|
||||
const sourceFile = program.getSourceFile(message.sourceFilePath);
|
||||
const sourceFile =
|
||||
message.sourceFilePath &&
|
||||
program.getSourceFile(message.sourceFilePath);
|
||||
if (!sourceFile) {
|
||||
throw new Error(
|
||||
`Failed to find source file in program at path "${message.sourceFilePath}"`,
|
||||
@@ -210,7 +223,7 @@ const ALLOW_WARNINGS = [
|
||||
async function resolvePackagePath(
|
||||
packagePath: string,
|
||||
): Promise<string | undefined> {
|
||||
const projectRoot = resolvePath(__dirname, '..');
|
||||
const projectRoot = resolvePath(process.cwd());
|
||||
const fullPackageDir = resolvePath(projectRoot, packagePath);
|
||||
|
||||
const stat = await fs.stat(fullPackageDir);
|
||||
@@ -228,7 +241,7 @@ async function resolvePackagePath(
|
||||
return relativePath(projectRoot, fullPackageDir);
|
||||
}
|
||||
|
||||
async function findSpecificPackageDirs(unresolvedPackageDirs: string[]) {
|
||||
export async function findSpecificPackageDirs(unresolvedPackageDirs: string[]) {
|
||||
const packageDirs = new Array<string>();
|
||||
|
||||
for (const unresolvedPackageDir of unresolvedPackageDirs) {
|
||||
@@ -246,9 +259,9 @@ async function findSpecificPackageDirs(unresolvedPackageDirs: string[]) {
|
||||
return packageDirs;
|
||||
}
|
||||
|
||||
async function findPackageDirs() {
|
||||
export async function findPackageDirs() {
|
||||
const packageDirs = new Array<string>();
|
||||
const projectRoot = resolvePath(__dirname, '..');
|
||||
const projectRoot = resolvePath(process.cwd());
|
||||
|
||||
for (const packageRoot of PACKAGE_ROOTS) {
|
||||
const dirs = await fs.readdir(resolvePath(projectRoot, packageRoot));
|
||||
@@ -265,8 +278,8 @@ async function findPackageDirs() {
|
||||
return packageDirs;
|
||||
}
|
||||
|
||||
async function createTemporaryTsConfig(includedPackageDirs: string[]) {
|
||||
const path = resolvePath(__dirname, '..', 'tsconfig.tmp.json');
|
||||
export async function createTemporaryTsConfig(includedPackageDirs: string[]) {
|
||||
const path = resolvePath(process.cwd(), 'tsconfig.tmp.json');
|
||||
|
||||
process.once('exit', () => {
|
||||
fs.removeSync(path);
|
||||
@@ -284,7 +297,7 @@ async function createTemporaryTsConfig(includedPackageDirs: string[]) {
|
||||
return path;
|
||||
}
|
||||
|
||||
async function countApiReportWarnings(projectFolder: string) {
|
||||
export async function countApiReportWarnings(projectFolder: string) {
|
||||
const path = resolvePath(projectFolder, 'api-report.md');
|
||||
try {
|
||||
const content = await fs.readFile(path, 'utf8');
|
||||
@@ -313,7 +326,7 @@ async function countApiReportWarnings(projectFolder: string) {
|
||||
}
|
||||
}
|
||||
|
||||
async function getTsDocConfig() {
|
||||
export async function getTsDocConfig() {
|
||||
const tsdocConfigFile = await TSDocConfigFile.loadFile(
|
||||
require.resolve('@microsoft/api-extractor/extends/tsdoc-base.json'),
|
||||
);
|
||||
@@ -349,7 +362,7 @@ interface ApiExtractionOptions {
|
||||
tsconfigFilePath: string;
|
||||
}
|
||||
|
||||
async function runApiExtraction({
|
||||
export async function runApiExtraction({
|
||||
packageDirs,
|
||||
outputDir,
|
||||
isLocalBuild,
|
||||
@@ -358,7 +371,10 @@ async function runApiExtraction({
|
||||
await fs.remove(outputDir);
|
||||
|
||||
const entryPoints = packageDirs.map(packageDir => {
|
||||
return resolvePath(__dirname, `../dist-types/${packageDir}/src/index.d.ts`);
|
||||
return resolvePath(
|
||||
process.cwd(),
|
||||
`./dist-types/${packageDir}/src/index.d.ts`,
|
||||
);
|
||||
});
|
||||
|
||||
let compilerState: CompilerState | undefined = undefined;
|
||||
@@ -367,8 +383,12 @@ async function runApiExtraction({
|
||||
|
||||
for (const packageDir of packageDirs) {
|
||||
console.log(`## Processing ${packageDir}`);
|
||||
const projectFolder = resolvePath(__dirname, '..', packageDir);
|
||||
const packageFolder = resolvePath(__dirname, '../dist-types', packageDir);
|
||||
const projectFolder = resolvePath(process.cwd(), packageDir);
|
||||
const packageFolder = resolvePath(
|
||||
process.cwd(),
|
||||
'./dist-types',
|
||||
packageDir,
|
||||
);
|
||||
|
||||
const warningCountBefore = await countApiReportWarnings(projectFolder);
|
||||
|
||||
@@ -618,7 +638,7 @@ class ApiModelTransforms {
|
||||
if (serialized.members.length !== 1) {
|
||||
throw new Error(
|
||||
`Unexpected members in serialized ApiPackage, [${serialized.members
|
||||
.map(m => m.kind)
|
||||
.map((m: { kind: any }) => m.kind)
|
||||
.join(' ')}]`,
|
||||
);
|
||||
}
|
||||
@@ -634,7 +654,7 @@ class ApiModelTransforms {
|
||||
members: [
|
||||
{
|
||||
...entryPoint,
|
||||
members: entryPoint.members.map(member =>
|
||||
members: entryPoint.members.map((member: any) =>
|
||||
transforms.reduce((m, t) => t(m), member),
|
||||
),
|
||||
},
|
||||
@@ -753,7 +773,7 @@ class ApiModelTransforms {
|
||||
};
|
||||
}
|
||||
|
||||
async function buildDocs({
|
||||
export async function buildDocs({
|
||||
inputDir,
|
||||
outputDir,
|
||||
}: {
|
||||
@@ -847,7 +867,11 @@ async function buildDocs({
|
||||
}
|
||||
|
||||
/** @override */
|
||||
emit(stringBuilder, docNode, options) {
|
||||
emit(
|
||||
stringBuilder: any,
|
||||
docNode: DocNode,
|
||||
options: ICustomMarkdownEmitterOptions,
|
||||
) {
|
||||
// Hack to get rid of the leading comment of each file, since
|
||||
// we want the front matter to come first
|
||||
stringBuilder._chunks.length = 0;
|
||||
@@ -886,7 +910,7 @@ async function buildDocs({
|
||||
// so we hook in wherever we can. In this case we add the front matter
|
||||
// just before writing the breadcrumbs at the top.
|
||||
/** @override */
|
||||
_writeBreadcrumb(output, apiItem) {
|
||||
_writeBreadcrumb(output: any, apiItem: ApiItem & { name: string }) {
|
||||
let title;
|
||||
let description;
|
||||
|
||||
@@ -897,9 +921,12 @@ async function buildDocs({
|
||||
} else if (apiItem.kind === 'Model') {
|
||||
title = 'Package Index';
|
||||
description = 'Index of all Backstage Packages';
|
||||
} else {
|
||||
} else if (apiItem.name) {
|
||||
title = apiItem.name;
|
||||
description = `API Reference for ${apiItem.name}`;
|
||||
} else {
|
||||
title = apiItem.displayName;
|
||||
description = `API Reference for ${apiItem.displayName}`;
|
||||
}
|
||||
|
||||
// Add our front matter
|
||||
@@ -925,7 +952,10 @@ async function buildDocs({
|
||||
};
|
||||
}
|
||||
|
||||
_writeModelTable(output, apiModel): void {
|
||||
_writeModelTable(
|
||||
output: { appendNode: (arg0: DocTable | DocHeading) => void },
|
||||
apiModel: { members: any },
|
||||
): void {
|
||||
const configuration = this._tsdocConfiguration;
|
||||
|
||||
const packagesTable = new DocTable({
|
||||
@@ -998,7 +1028,10 @@ async function buildDocs({
|
||||
documenter.generateFiles();
|
||||
}
|
||||
|
||||
async function categorizePackageDirs(projectRoot, packageDirs) {
|
||||
export async function categorizePackageDirs(
|
||||
projectRoot: string,
|
||||
packageDirs: any[],
|
||||
) {
|
||||
const dirs = packageDirs.slice();
|
||||
const tsPackageDirs = new Array<string>();
|
||||
const cliPackageDirs = new Array<string>();
|
||||
@@ -1086,11 +1119,11 @@ function parseHelpPage(helpPageContent: string) {
|
||||
// Trim away documentation
|
||||
const sectionItems = sectionLines
|
||||
.map(line => line.match(/^\s{1,8}(.*?)\s\s+/)?.[1])
|
||||
.filter(Boolean);
|
||||
.filter(Boolean) as string[];
|
||||
|
||||
if (sectionName.toLocaleLowerCase('en-US') === 'options:') {
|
||||
if (sectionName?.toLocaleLowerCase('en-US') === 'options:') {
|
||||
options = sectionItems;
|
||||
} else if (sectionName.toLocaleLowerCase('en-US') === 'commands:') {
|
||||
} else if (sectionName?.toLocaleLowerCase('en-US') === 'commands:') {
|
||||
commands = sectionItems;
|
||||
} else {
|
||||
throw new Error(`Unknown CLI section: ${sectionName}`);
|
||||
@@ -1185,7 +1218,7 @@ interface CliExtractionOptions {
|
||||
isLocalBuild: boolean;
|
||||
}
|
||||
|
||||
async function runCliExtraction({
|
||||
export async function runCliExtraction({
|
||||
projectRoot,
|
||||
packageDirs,
|
||||
isLocalBuild,
|
||||
@@ -1250,96 +1283,3 @@ async function runCliExtraction({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const projectRoot = resolvePath(__dirname, '..');
|
||||
const isCiBuild = process.argv.includes('--ci');
|
||||
const isDocsBuild = process.argv.includes('--docs');
|
||||
const runTsc = process.argv.includes('--tsc');
|
||||
|
||||
const selectedPackageDirs = await findSpecificPackageDirs(
|
||||
process.argv.slice(2).filter(arg => !arg.startsWith('--')),
|
||||
);
|
||||
if (selectedPackageDirs && isCiBuild) {
|
||||
throw new Error(
|
||||
'Package path arguments are not supported together with the --ci flag',
|
||||
);
|
||||
}
|
||||
if (!selectedPackageDirs && !isCiBuild && !isDocsBuild) {
|
||||
console.log('');
|
||||
console.log(
|
||||
'TIP: You can generate api-reports for select packages by passing package paths:',
|
||||
);
|
||||
console.log('');
|
||||
console.log(
|
||||
' yarn build:api-reports packages/config packages/core-plugin-api',
|
||||
);
|
||||
console.log('');
|
||||
}
|
||||
|
||||
let temporaryTsConfigPath: string | undefined;
|
||||
if (selectedPackageDirs) {
|
||||
temporaryTsConfigPath = await createTemporaryTsConfig(selectedPackageDirs);
|
||||
}
|
||||
const tsconfigFilePath =
|
||||
temporaryTsConfigPath ?? resolvePath(projectRoot, 'tsconfig.json');
|
||||
|
||||
if (runTsc) {
|
||||
await fs.remove(resolvePath(projectRoot, 'dist-types'));
|
||||
const { status } = spawnSync(
|
||||
'yarn',
|
||||
[
|
||||
'tsc',
|
||||
['--project', tsconfigFilePath],
|
||||
['--skipLibCheck', 'false'],
|
||||
['--incremental', 'false'],
|
||||
].flat(),
|
||||
{
|
||||
stdio: 'inherit',
|
||||
shell: true,
|
||||
cwd: projectRoot,
|
||||
},
|
||||
);
|
||||
if (status !== 0) {
|
||||
process.exit(status);
|
||||
}
|
||||
}
|
||||
|
||||
const packageDirs = selectedPackageDirs ?? (await findPackageDirs());
|
||||
|
||||
const { tsPackageDirs, cliPackageDirs } = await categorizePackageDirs(
|
||||
projectRoot,
|
||||
packageDirs,
|
||||
);
|
||||
|
||||
if (tsPackageDirs.length > 0) {
|
||||
console.log('# Generating package API reports');
|
||||
await runApiExtraction({
|
||||
packageDirs: tsPackageDirs,
|
||||
outputDir: tmpDir,
|
||||
isLocalBuild: !isCiBuild,
|
||||
tsconfigFilePath,
|
||||
});
|
||||
}
|
||||
if (cliPackageDirs.length > 0) {
|
||||
console.log('# Generating package CLI reports');
|
||||
await runCliExtraction({
|
||||
projectRoot,
|
||||
packageDirs: cliPackageDirs,
|
||||
isLocalBuild: !isCiBuild,
|
||||
});
|
||||
}
|
||||
|
||||
if (isDocsBuild) {
|
||||
console.log('# Generating package documentation');
|
||||
await buildDocs({
|
||||
inputDir: tmpDir,
|
||||
outputDir: resolvePath(projectRoot, 'docs/reference'),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(error => {
|
||||
console.error(error.stack || String(error));
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright 2022 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 { OptionValues } from 'commander';
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import fs from 'fs-extra';
|
||||
import { spawnSync } from 'child_process';
|
||||
import {
|
||||
findSpecificPackageDirs,
|
||||
createTemporaryTsConfig,
|
||||
findPackageDirs,
|
||||
categorizePackageDirs,
|
||||
runApiExtraction,
|
||||
runCliExtraction,
|
||||
buildDocs,
|
||||
} from './api-extractor';
|
||||
|
||||
export default async (paths: string[], opts: OptionValues) => {
|
||||
const tmpDir = resolvePath(
|
||||
process.cwd(),
|
||||
'./node_modules/.cache/api-extractor',
|
||||
);
|
||||
const projectRoot = resolvePath(process.cwd());
|
||||
const isCiBuild = opts.ci;
|
||||
const isDocsBuild = opts.docs;
|
||||
const runTsc = opts.tsc;
|
||||
|
||||
const selectedPackageDirs = await findSpecificPackageDirs(paths);
|
||||
|
||||
if (selectedPackageDirs && isCiBuild) {
|
||||
throw new Error(
|
||||
'Package path arguments are not supported together with the --ci flag',
|
||||
);
|
||||
}
|
||||
if (!selectedPackageDirs && !isCiBuild && !isDocsBuild) {
|
||||
console.log('');
|
||||
console.log(
|
||||
'TIP: You can generate api-reports for select packages by passing package paths:',
|
||||
);
|
||||
console.log('');
|
||||
console.log(
|
||||
' yarn build:api-reports packages/config packages/core-plugin-api',
|
||||
);
|
||||
console.log('');
|
||||
}
|
||||
|
||||
let temporaryTsConfigPath: string | undefined;
|
||||
if (selectedPackageDirs) {
|
||||
temporaryTsConfigPath = await createTemporaryTsConfig(selectedPackageDirs);
|
||||
}
|
||||
const tsconfigFilePath =
|
||||
temporaryTsConfigPath ?? resolvePath(projectRoot, 'tsconfig.json');
|
||||
|
||||
if (runTsc) {
|
||||
await fs.remove(resolvePath(projectRoot, 'dist-types'));
|
||||
const { status } = spawnSync(
|
||||
'yarn',
|
||||
[
|
||||
'tsc',
|
||||
['--project', tsconfigFilePath],
|
||||
['--skipLibCheck', 'false'],
|
||||
['--incremental', 'false'],
|
||||
].flat(),
|
||||
{
|
||||
stdio: 'inherit',
|
||||
shell: true,
|
||||
cwd: projectRoot,
|
||||
},
|
||||
);
|
||||
if (status !== 0) {
|
||||
process.exit(status || undefined);
|
||||
}
|
||||
}
|
||||
|
||||
const packageDirs = selectedPackageDirs ?? (await findPackageDirs());
|
||||
|
||||
const { tsPackageDirs, cliPackageDirs } = await categorizePackageDirs(
|
||||
projectRoot,
|
||||
packageDirs,
|
||||
);
|
||||
|
||||
if (tsPackageDirs.length > 0) {
|
||||
console.log('# Generating package API reports');
|
||||
await runApiExtraction({
|
||||
packageDirs: tsPackageDirs,
|
||||
outputDir: tmpDir,
|
||||
isLocalBuild: !isCiBuild,
|
||||
tsconfigFilePath,
|
||||
});
|
||||
}
|
||||
if (cliPackageDirs.length > 0) {
|
||||
console.log('# Generating package CLI reports');
|
||||
await runCliExtraction({
|
||||
projectRoot,
|
||||
packageDirs: cliPackageDirs,
|
||||
isLocalBuild: !isCiBuild,
|
||||
});
|
||||
}
|
||||
|
||||
if (isDocsBuild) {
|
||||
console.log('# Generating package documentation');
|
||||
await buildDocs({
|
||||
inputDir: tmpDir,
|
||||
outputDir: resolvePath(projectRoot, 'docs/reference'),
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2022 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 { assertError } from '@backstage/errors';
|
||||
import { Command } from 'commander';
|
||||
import { exitWithError } from '../lib/errors';
|
||||
|
||||
export function registerCommands(program: Command) {
|
||||
program
|
||||
.command('api-reports [path...]')
|
||||
.option('--ci', 'CI run checks that there is no changes on API reports')
|
||||
.option('--tsc', 'executes the tsc compilation before extracting the APIs')
|
||||
.option('--docs', 'generates the api documentation')
|
||||
.description('Generate an API report for selected packages')
|
||||
.action(
|
||||
lazy(() => import('./api-reports/api-reports').then(m => m.default)),
|
||||
);
|
||||
}
|
||||
|
||||
// Wraps an action function so that it always exits and handles errors
|
||||
function lazy(
|
||||
getActionFunc: () => Promise<(...args: any[]) => Promise<void>>,
|
||||
): (...args: any[]) => Promise<never> {
|
||||
return async (...args: any[]) => {
|
||||
try {
|
||||
const actionFunc = await getActionFunc();
|
||||
await actionFunc(...args);
|
||||
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
assertError(error);
|
||||
exitWithError(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* CLI for Backstage repo tooling
|
||||
*
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
import { program } from 'commander';
|
||||
import chalk from 'chalk';
|
||||
import { exitWithError } from './lib/errors';
|
||||
import { registerCommands } from './commands';
|
||||
|
||||
const main = (argv: string[]) => {
|
||||
program.name('backstage-repo-tools').version('1.0');
|
||||
|
||||
registerCommands(program);
|
||||
|
||||
program.on('command:*', () => {
|
||||
console.log();
|
||||
console.log(chalk.red(`Invalid command: ${program.args.join(' ')}`));
|
||||
console.log();
|
||||
program.outputHelp();
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
program.parse(argv);
|
||||
};
|
||||
|
||||
process.on('unhandledRejection', rejection => {
|
||||
if (rejection instanceof Error) {
|
||||
exitWithError(rejection);
|
||||
} else {
|
||||
exitWithError(new Error(`Unknown rejection: '${rejection}'`));
|
||||
}
|
||||
});
|
||||
|
||||
main(process.argv);
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2020 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 chalk from 'chalk';
|
||||
|
||||
export class CustomError extends Error {
|
||||
get name(): string {
|
||||
return this.constructor.name;
|
||||
}
|
||||
}
|
||||
|
||||
export class ExitCodeError extends CustomError {
|
||||
readonly code: number;
|
||||
|
||||
constructor(code: number, command?: string) {
|
||||
super(
|
||||
command
|
||||
? `Command '${command}' exited with code ${code}`
|
||||
: `Child exited with code ${code}`,
|
||||
);
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
|
||||
export function exitWithError(error: Error): never {
|
||||
if (error instanceof ExitCodeError) {
|
||||
process.stderr.write(`\n${chalk.red(error.message)}\n\n`);
|
||||
process.exit(error.code);
|
||||
} else {
|
||||
process.stderr.write(`\n${chalk.red(`${error}`)}\n\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
export class NotFoundError extends CustomError {}
|
||||
@@ -7653,6 +7653,24 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/repo-tools@workspace:*, @backstage/repo-tools@workspace:packages/repo-tools":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/repo-tools@workspace:packages/repo-tools"
|
||||
dependencies:
|
||||
"@backstage/errors": "workspace:^"
|
||||
"@microsoft/api-documenter": ^7.17.11
|
||||
"@microsoft/api-extractor": ^7.23.0
|
||||
"@microsoft/api-extractor-model": ^7.17.2
|
||||
"@microsoft/tsdoc": 0.14.1
|
||||
chalk: ^4.0.0
|
||||
commander: ^9.1.0
|
||||
fs-extra: 10.1.0
|
||||
ts-node: ^10.0.0
|
||||
bin:
|
||||
backstage-repo-tools: bin/backstage-repo-tools
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/test-utils@workspace:^, @backstage/test-utils@workspace:packages/test-utils":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/test-utils@workspace:packages/test-utils"
|
||||
@@ -10487,13 +10505,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@microsoft/tsdoc@npm:^0.14.1":
|
||||
version: 0.14.2
|
||||
resolution: "@microsoft/tsdoc@npm:0.14.2"
|
||||
checksum: b167c89e916ba73ee20b9c9d5dba6aa3a0de25ed3d50050e8a344dca7cd43cb2e1059bd515c820369b6e708901dd3fda476a42bc643ca74a35671ce77f724a3a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@mswjs/cookies@npm:^0.2.0, @mswjs/cookies@npm:^0.2.2":
|
||||
version: 0.2.2
|
||||
resolution: "@mswjs/cookies@npm:0.2.2"
|
||||
@@ -33053,12 +33064,9 @@ __metadata:
|
||||
"@backstage/codemods": "workspace:*"
|
||||
"@backstage/create-app": "workspace:*"
|
||||
"@backstage/errors": "workspace:^"
|
||||
"@backstage/repo-tools": "workspace:*"
|
||||
"@changesets/cli": ^2.14.0
|
||||
"@manypkg/get-packages": ^1.1.3
|
||||
"@microsoft/api-documenter": ^7.17.11
|
||||
"@microsoft/api-extractor": ^7.23.0
|
||||
"@microsoft/api-extractor-model": ^7.17.2
|
||||
"@microsoft/tsdoc": ^0.14.1
|
||||
"@octokit/rest": ^19.0.3
|
||||
"@spotify/prettier-config": ^14.0.0
|
||||
"@techdocs/cli": "workspace:*"
|
||||
|
||||
Reference in New Issue
Block a user