generate-typescript-docs.ts 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. /* eslint-disable no-console */
  2. import fs from 'fs-extra';
  3. import klawSync from 'klaw-sync';
  4. import path, { extname } from 'path';
  5. import { deleteGeneratedDocs, normalizeForUrlPart } from './docgen-utils';
  6. import { TypeMap } from './typescript-docgen-types';
  7. import { TypescriptDocsParser } from './typescript-docs-parser';
  8. import { TypescriptDocsRenderer } from './typescript-docs-renderer';
  9. interface DocsSectionConfig {
  10. sourceDirs: string[];
  11. exclude?: RegExp[];
  12. outputPath: string;
  13. }
  14. const sections: DocsSectionConfig[] = [
  15. {
  16. sourceDirs: [
  17. 'packages/core/src/',
  18. 'packages/common/src/',
  19. 'packages/admin-ui-plugin/src/',
  20. 'packages/asset-server-plugin/src/',
  21. 'packages/email-plugin/src/',
  22. 'packages/elasticsearch-plugin/src/',
  23. 'packages/job-queue-plugin/src/',
  24. 'packages/payments-plugin/src/',
  25. 'packages/testing/src/',
  26. 'packages/harden-plugin/src/',
  27. ],
  28. exclude: [/generated-shop-types/],
  29. outputPath: 'typescript-api',
  30. },
  31. {
  32. sourceDirs: ['packages/admin-ui/src/lib/', 'packages/ui-devkit/src/'],
  33. exclude: [/generated-types/],
  34. outputPath: 'admin-ui-api',
  35. },
  36. ];
  37. generateTypescriptDocs(sections);
  38. const watchMode = !!process.argv.find(arg => arg === '--watch' || arg === '-w');
  39. if (watchMode) {
  40. console.log(`Watching for changes to source files...`);
  41. sections.forEach(section => {
  42. section.sourceDirs.forEach(dir => {
  43. fs.watch(dir, { recursive: true }, (eventType, file) => {
  44. if (extname(file) === '.ts') {
  45. console.log(`Changes detected in ${dir}`);
  46. generateTypescriptDocs([section], true);
  47. }
  48. });
  49. });
  50. });
  51. }
  52. /**
  53. * Uses the TypeScript compiler API to parse the given files and extract out the documentation
  54. * into markdown files
  55. */
  56. function generateTypescriptDocs(config: DocsSectionConfig[], isWatchMode: boolean = false) {
  57. const timeStart = +new Date();
  58. // This map is used to cache types and their corresponding Hugo path. It is used to enable
  59. // hyperlinking from a member's "type" to the definition of that type.
  60. const globalTypeMap: TypeMap = new Map();
  61. if (!isWatchMode) {
  62. for (const { outputPath, sourceDirs } of config) {
  63. deleteGeneratedDocs(absOutputPath(outputPath));
  64. }
  65. }
  66. for (const { outputPath, sourceDirs, exclude } of config) {
  67. const sourceFilePaths = getSourceFilePaths(sourceDirs, exclude);
  68. const docsPages = new TypescriptDocsParser().parse(sourceFilePaths);
  69. for (const page of docsPages) {
  70. const { category, fileName, declarations } = page;
  71. for (const declaration of declarations) {
  72. const pathToTypeDoc = `${outputPath}/${
  73. category ? category.map(part => normalizeForUrlPart(part)).join('/') + '/' : ''
  74. }${fileName === '_index' ? '' : fileName}#${toHash(declaration.title)}`;
  75. globalTypeMap.set(declaration.title, pathToTypeDoc);
  76. }
  77. }
  78. const docsUrl = ``;
  79. const generatedCount = new TypescriptDocsRenderer().render(
  80. docsPages,
  81. docsUrl,
  82. absOutputPath(outputPath),
  83. globalTypeMap,
  84. );
  85. if (generatedCount) {
  86. console.log(
  87. `Generated ${generatedCount} typescript api docs for "${outputPath}" in ${
  88. +new Date() - timeStart
  89. }ms`,
  90. );
  91. }
  92. }
  93. }
  94. function toHash(title: string): string {
  95. return title.replace(/\s/g, '').toLowerCase();
  96. }
  97. function absOutputPath(outputPath: string): string {
  98. return path.join(__dirname, '../../docs/content/', outputPath);
  99. }
  100. function getSourceFilePaths(sourceDirs: string[], excludePatterns: RegExp[] = []): string[] {
  101. return sourceDirs
  102. .map(scanPath =>
  103. klawSync(path.join(__dirname, '../../', scanPath), {
  104. nodir: true,
  105. filter: item => {
  106. if (path.extname(item.path) === '.ts') {
  107. for (const pattern of excludePatterns) {
  108. if (pattern.test(item.path)) {
  109. return false;
  110. }
  111. }
  112. return true;
  113. }
  114. return false;
  115. },
  116. traverseAll: true,
  117. }),
  118. )
  119. .reduce((allFiles, files) => [...allFiles, ...files], [])
  120. .map(item => item.path);
  121. }