Design system.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

data.ts 4.1 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. import { readFile } from 'fs/promises';
  2. import { dirname, basename, resolve } from 'path';
  3. import { glob } from 'glob';
  4. import { minimatch } from 'minimatch';
  5. export interface AmanuensisConfig {
  6. package: {
  7. searchPatterns: string | string[];
  8. classifications: Record<string, Record<string, string | string[]>>;
  9. }
  10. }
  11. interface TypedocDataTextNode {
  12. kind: 'text';
  13. text: string;
  14. }
  15. interface TypedocDataInlineTagNode {
  16. kind: 'inline-tag';
  17. tag: string;
  18. text: string;
  19. target: number;
  20. tsLinkText: string;
  21. }
  22. interface SymbolIdMapEntry {
  23. sourceFileName: string;
  24. qualifiedName: string;
  25. }
  26. type TypedocDataNode = TypedocDataTextNode | TypedocDataInlineTagNode;
  27. export interface TypedocData {
  28. packages: any[];
  29. typedocData: {
  30. readme: TypedocDataNode[];
  31. symbolIdMap: Record<string, SymbolIdMapEntry>;
  32. };
  33. }
  34. export const getReadmeText = async (cwd = process.cwd()) => {
  35. const typedocDataJsonPath = resolve(cwd, '.amanuensis', 'data.json');
  36. const typedocDataJson = await readFile(typedocDataJsonPath, 'utf-8');
  37. const typedocData = JSON.parse(typedocDataJson) as {
  38. typedocData: {
  39. readme: { kind: string, text: string }[]
  40. }
  41. };
  42. return typedocData.typedocData.readme.reduce(
  43. (theText, node) => {
  44. if (node.kind === 'text') {
  45. return `${theText}${node.text}`;
  46. }
  47. return theText;
  48. },
  49. '',
  50. );
  51. };
  52. export const getPackages = async (cwd = process.cwd()) => {
  53. const configPath = resolve(cwd, '.amanuensis', 'config.json');
  54. const configString = await readFile(configPath, 'utf-8');
  55. const config = JSON.parse(configString) as AmanuensisConfig;
  56. const searchPatternsRaw = config.package.searchPatterns;
  57. const searchPatterns = Array.isArray(searchPatternsRaw) ? searchPatternsRaw : [searchPatternsRaw];
  58. const patternPackagePaths = await Promise.all(
  59. searchPatterns.map(async (searchPattern) => glob(
  60. searchPattern === 'package.json' || searchPattern.endsWith('/package.json')
  61. ? searchPattern
  62. : `${searchPattern}/package.json`,
  63. {
  64. ignore: ['**/node_modules/**'],
  65. },
  66. )),
  67. );
  68. const packagePaths = patternPackagePaths.flat();
  69. const markdownFilePaths = await glob(
  70. '**/*.{md,mdx}',
  71. {
  72. ignore: ['**/node_modules/**'],
  73. },
  74. );
  75. return Promise.all(
  76. packagePaths.map(async (packagePath) => {
  77. const packageString = await readFile(packagePath, 'utf-8');
  78. const basePath = dirname(packagePath);
  79. const packageJson = JSON.parse(packageString) as { name: string };
  80. const classifications = Object.fromEntries(
  81. Object.entries(config.package.classifications)
  82. .map(([classification, c]) => {
  83. const [thisClassifications] = Object.entries(c)
  84. .find(([, globRaw]) => {
  85. const globs = Array.isArray(globRaw) ? globRaw : [globRaw];
  86. return globs.some((g) => minimatch(packageJson.name, g));
  87. }) ?? [];
  88. return [classification, thisClassifications] as const;
  89. }),
  90. );
  91. const markdownFiles = markdownFilePaths.filter((markdownFilePath) => (
  92. markdownFilePath.startsWith(basePath)
  93. ));
  94. const markdown = await Promise.all(
  95. markdownFiles.map(async (markdownFilePath) => {
  96. const content = await readFile(markdownFilePath, 'utf-8');
  97. const filePath = markdownFilePath.slice(basePath.length + 1);
  98. const file = filePath.split('/').at(-1) ?? '';
  99. const name = filePath === 'README.md' ? 'index.md' : basename(file);
  100. return {
  101. name,
  102. filePath,
  103. content,
  104. };
  105. }),
  106. );
  107. return {
  108. name: packageJson.name,
  109. packageJson,
  110. basePath,
  111. markdown,
  112. classifications,
  113. };
  114. }),
  115. );
  116. };
  117. export const getFileSources = async (cwd = process.cwd()) => {
  118. const typedocDataJsonPath = resolve(cwd, '.amanuensis', 'data.json');
  119. const typedocDataJson = await readFile(typedocDataJsonPath, 'utf-8');
  120. const typedocData = JSON.parse(typedocDataJson) as TypedocData;
  121. const symbolIdMapEntries = Object.values(typedocData.typedocData.symbolIdMap);
  122. const firstPartySources = symbolIdMapEntries.filter(
  123. ({ sourceFileName }) => !sourceFileName.startsWith('node_modules'),
  124. );
  125. const firstPartySourceFiles = firstPartySources.map(({ sourceFileName }) => sourceFileName);
  126. const uniqueFirstPartySourceFiles = [...new Set(firstPartySourceFiles)];
  127. return uniqueFirstPartySourceFiles;
  128. };