docgen: add proper cli entrypoint end improve path handling

This commit is contained in:
Patrik Oldsberg
2020-07-16 15:00:00 +02:00
parent f668388c9e
commit b1558c5af7
5 changed files with 83 additions and 75 deletions
@@ -36,17 +36,12 @@ import {
* While traversing, it collects information such as names, location in source, docs, etc.
*/
export default class ApiDocGenerator {
static fromProgram(
program: ts.Program,
basePath: string,
sourcePath: string,
) {
return new ApiDocGenerator(program.getTypeChecker(), basePath, sourcePath);
static fromProgram(program: ts.Program, sourcePath: string) {
return new ApiDocGenerator(program.getTypeChecker(), sourcePath);
}
constructor(
private readonly checker: ts.TypeChecker,
private readonly basePath: string,
private readonly sourcePath: string,
) {}
@@ -96,7 +91,7 @@ export default class ApiDocGenerator {
const name = (type.aliasSymbol || type.symbol).name;
const [declaration] = (type.aliasSymbol || type.symbol).declarations;
const sourceFile = declaration.getSourceFile();
const file = relative(this.basePath, sourceFile.fileName);
const file = relative(this.sourcePath, sourceFile.fileName);
const { line } = sourceFile.getLineAndCharacterOfPosition(
declaration.getStart(),
);
@@ -210,7 +205,7 @@ export default class ApiDocGenerator {
const { line } = sourceFile.getLineAndCharacterOfPosition(
declaration.getStart(),
);
const file = relative(this.basePath, sourceFile.fileName);
const file = relative(this.sourcePath, sourceFile.fileName);
const typeInfo = {
id: (symbol as any).id,
name: symbol.name,
+3 -1
View File
@@ -19,7 +19,9 @@ import MarkdownPrinter from './MarkdownPrinter';
import sortSelector from './sortSelector';
import { ApiDoc, InterfaceInfo } from './types';
// TODO(Rugvip): provide through options?
const GH_BASE_URL = 'https://github.com/spotify/backstage';
const SRC_PATH = 'packages/core-api/src';
const COMMIT_SHA =
process.env.COMMIT_SHA || execSync('git rev-parse HEAD').toString('utf8');
@@ -37,7 +39,7 @@ export default class ApiDocPrinter {
mkTypeLink({ file, lineInFile }: { file: string; lineInFile: number }) {
const text = `${file}:${lineInFile}`;
const href = `${GH_BASE_URL}/blob/${COMMIT_SHA}/${file}#L${lineInFile}`;
const href = `${GH_BASE_URL}/blob/${COMMIT_SHA}/${SRC_PATH}/${file}#L${lineInFile}`;
return `[${text}](${href}){:target="_blank"}`;
}
+8 -11
View File
@@ -24,17 +24,15 @@ import { ExportedInstance } from './types';
* This is used to e.g. find exported APIs that we should generate documentation for.
*/
export default class TypeLocator {
private readonly checker: ts.TypeChecker;
private readonly program: ts.Program;
static fromProgram(program: ts.Program) {
return new TypeLocator(program.getTypeChecker(), program);
static fromProgram(program: ts.Program, sourcePath: string) {
return new TypeLocator(program.getTypeChecker(), program, sourcePath);
}
constructor(checker: ts.TypeChecker, program: ts.Program) {
this.checker = checker;
this.program = program;
}
constructor(
private readonly checker: ts.TypeChecker,
private readonly program: ts.Program,
private readonly sourcePath: string,
) {}
getExportedType(path: string, exportedName: string = 'default'): ts.Type {
const source = this.program.getSourceFile(path);
@@ -54,7 +52,6 @@ export default class TypeLocator {
findExportedInstances<T extends string>(
typeLookupTable: { [key in T]: ts.Type },
roots: string[],
): { [key in T]: ExportedInstance[] } {
const docMap = new Map<ts.Type, ExportedInstance[]>();
for (const type of Object.values<ts.Type>(typeLookupTable)) {
@@ -62,7 +59,7 @@ export default class TypeLocator {
}
this.program.getSourceFiles().forEach(source => {
const inRoot = roots.some(root => source.fileName.startsWith(root));
const inRoot = source.fileName.startsWith(this.sourcePath);
if (!inRoot) {
return;
}
@@ -15,9 +15,8 @@
*/
import * as ts from 'typescript';
import { resolve, join, dirname } from 'path';
import { promisify } from 'util';
import fs from 'fs-extra';
import { resolve as resolvePath, join as joinPath } from 'path';
import ApiDocGenerator from './docgen/ApiDocGenerator';
import sortSelector from './docgen/sortSelector';
import TypeLocator from './docgen/TypeLocator';
@@ -25,49 +24,29 @@ import ApiDocPrinter from './docgen/ApiDocPrinter';
import TypescriptHighlighter from './docgen/TypescriptHighlighter';
import MarkdownPrinter from './docgen/MarkdownPrinter';
const writeFile = promisify(fs.writeFile);
export async function generate(targetPath: string) {
const rootDir = resolvePath(__dirname, '..');
const srcDir = resolvePath(rootDir, '..', 'core-api', 'src');
const targetDir = resolvePath(targetPath);
const docsDir = resolvePath(targetDir, 'docs');
function loadOptions(path: string): ts.CompilerOptions {
const config: any = require(path);
let parent = config.extends as string | undefined;
if (!parent) {
return config.compilerOptions;
}
if (parent.startsWith('.')) {
parent = join(dirname(path), parent);
}
return { ...loadOptions(parent), ...config.compilerOptions };
}
async function main() {
const rootDir = resolve(__dirname, '..');
const srcDir = resolve(rootDir, '..', 'core-api', 'src');
const entrypoint = resolve(srcDir, 'index.ts');
const apiRefsDir = resolve(rootDir, 'dist');
const mkdocsYaml = resolve(apiRefsDir, 'mkdocs.yml');
process.chdir(rootDir);
const options = loadOptions('../../../tsconfig.json');
const options = await fs.readJson(resolvePath('../cli/config/tsconfig.json'));
delete options.moduleResolution;
options.removeComments = false;
options.noEmit = true;
const program = ts.createProgram([entrypoint], options);
const program = ts.createProgram([resolvePath(srcDir, 'index.ts')], options);
const typeLocator = TypeLocator.fromProgram(program);
const typeLocator = TypeLocator.fromProgram(program, srcDir);
const { apis } = typeLocator.findExportedInstances(
{
apis: typeLocator.getExportedType(entrypoint, 'createApiRef'),
},
[srcDir],
);
const { apis } = typeLocator.findExportedInstances({
apis: typeLocator.getExportedType(
resolvePath(srcDir, 'index.ts'),
'createApiRef',
),
});
const apiDocGenerator = ApiDocGenerator.fromProgram(program, rootDir, srcDir);
const apiDocGenerator = ApiDocGenerator.fromProgram(program, srcDir);
const apiDocs = apis
.map(api => {
try {
@@ -90,23 +69,21 @@ async function main() {
() => new MarkdownPrinter(new TypescriptHighlighter()),
);
fs.ensureDirSync(resolve(apiRefsDir, 'docs'));
fs.ensureDirSync(docsDir);
await writeFile(
join(apiRefsDir, 'docs', 'README.md'),
await fs.writeFile(
joinPath(docsDir, 'README.md'),
apiDocPrinter.printApiIndex(apiDocs),
);
await Promise.all(
Object.values(apiTypes).map(apiType => {
const data = apiDocPrinter.printInterface(apiType, apiDocs);
for (const apiType of Object.values(apiTypes)) {
const data = apiDocPrinter.printInterface(apiType, apiDocs);
return writeFile(join(apiRefsDir, 'docs', `${apiType.name}.md`), data);
}),
);
await fs.writeFile(joinPath(docsDir, `${apiType.name}.md`), data);
}
fs.writeFileSync(
mkdocsYaml,
await fs.writeFile(
resolvePath(targetDir, 'mkdocs.yml'),
[
'site_name: api-references',
'nav:',
@@ -118,8 +95,3 @@ async function main() {
'utf8',
);
}
main().catch(error => {
console.error(error.stack || error);
process.exit(1);
});
+43 -1
View File
@@ -14,4 +14,46 @@
* limitations under the License.
*/
import './compiler';
import program from 'commander';
import { resolve as resolvePath } from 'path';
import chalk from 'chalk';
import fs from 'fs-extra';
import { generate } from './generate';
const main = (argv: string[]) => {
const pkgJson = fs.readJsonSync(resolvePath(__dirname, '../package.json'));
program.name('docgen').version(pkgJson.version);
program
.command('generate')
.description(
'Generate documentation for the declarations in the core-api package',
)
.option('--output <output>', 'Output directory [./dist]')
.action(async cmd => {
await generate(cmd.output ?? './dist');
});
program.on('command:*', () => {
console.log();
console.log(
chalk.red(`Invalid command: ${chalk.cyan(program.args.join(' '))}`),
);
console.log(chalk.red('See --help for a list of available commands.'));
console.log();
process.exit(1);
});
if (!process.argv.slice(2).length) {
program.outputHelp(chalk.yellow);
}
program.parse(argv);
};
process.on('unhandledRejection', rejection => {
console.error(String(rejection));
process.exit(1);
});
main(process.argv);